├── .gitignore ├── LICENSE ├── README.md ├── conf ├── crs-setup.conf └── modsecurity.conf ├── main.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModSecurity CLI 2 | 3 | A CLI wrapper for libmodsecurity to quickly test payloads against Rules in a headless mode, without having to set up a full-fledged web testing environment. 4 | 5 | This wrapper is still in development, and some ModSecurity features could be missing. 6 | Most ModSecurity methods are implemented via [pymodsecurity](https://github.com/AvalZ/pymodsecurity) (requires manual building -- [PR](https://github.com/pymodsecurity/pymodsecurity/pull/21) pending on the [official repository](https://github.com/pymodsecurity/pymodsecurity)), 7 | 8 | ## Getting started 9 | 10 | To run `modsecurity-cli`, you will need a few setup steps. 11 | 12 | ### Setup 13 | 14 | 1. [Compile and Install ModSecurity v3.0.10](#compile-modsecurity-v3010) 15 | 1. [Install pymodsecurity](#install-pymodsecurity) 16 | 1. [Clone the OWASP CoreRuleSet](#clone-the-owasp-coreruleset) 17 | 1. [Run the CLI!](#run-the-cli) 18 | 19 | Here's the detail for each step. 20 | 21 | ### Compile ModSecurity v3.0.10 22 | 23 | First of all, you will need to install [ModSecurity v3.0.10](https://github.com/SpiderLabs/ModSecurity/releases/tag/v3.0.10) on your system. 24 | Currently, this is a ~~nightmare~~tricky process, since you will need to [build ModSecurity v3.0.10 from source](https://github.com/SpiderLabs/ModSecurity/wiki/Compilation-recipes-for-v3.x) 25 | (although some distros might have an updated registry with ModSecurity 3.0.10 already available `*coff*arch*coff*`) 26 | 27 | ### Install pymodsecurity 28 | 29 | 30 | In `modsecurity-cli` ModSecurity methods are implemented via [pymodsecurity](https://github.com/pymodsecurity/pymodsecurity). 31 | Since development on the official repository stopped on ModSecurity v3.0.3, we opened a [PR](https://github.com/pymodsecurity/pymodsecurity/pull/21). 32 | 33 | Current workaround: clone [our fork](https://github.com/AvalZ/pymodsecurity) and [build it from source](https://github.com/AvalZ/pymodsecurity#building-from-source) 34 | 35 | ### Clone the OWASP CoreRuleSet 36 | 37 | To detect incoming payloads, you need a Rule Set. 38 | The *de facto* standard is the [OWASP CoreRuleSet](https://github.com/coreruleset/coreruleset), but of course you can choose any Rule Set you want, or customize the OWASP CRS. 39 | 40 | To run the recommended settings, just clone the OWASP CRS in the project folder: 41 | ``` 42 | git clone git@github.com:coreruleset/coreruleset.git 43 | ``` 44 | 45 | ### Run the CLI! 46 | 47 | Check ModSecurity version 48 | 49 | ```console 50 | $ python3 main.py --version 51 | 52 | ``` 53 | 54 | Evaluate a single parameter 55 | 56 | ```console 57 | $ python3 main.py "" 58 | 59 | 20 60 | ``` 61 | 62 | If you want to see the breakdown of matched rules, just use `--verbose` 63 | 64 | ```console 65 | $ python3 main.py "" --verbose 66 | 67 | GET http://www.modsecurity.org/test?q=%3Cscript%3Ealert%281%29%3C%2Fscript%3E 68 | 69 | # Matched rules 70 | 71 | - 920320 [+2/PL2] - Missing User Agent Header 72 | - 920273 [+5/PL4] - Invalid character in request (outside of very strict set) 73 | + 941100 [+5/PL1] - XSS Attack Detected via libinjection 74 | + 941110 [+5/PL1] - XSS Filter - Category 1: Script Tag Vector 75 | + 941160 [+5/PL1] - NoScript XSS InjectionChecker: HTML Injection 76 | + 941390 [+5/PL1] - Javascript method detected 77 | - 941320 [+5/PL2] - Possible XSS Attack Detected - HTML Tag Handler 78 | - 942131 [+5/PL2] - SQL Injection Attack: SQL Boolean-based attack detected 79 | - 942431 [+3/PL3] - Restricted SQL Character Anomaly Detection (args): # of special characters exceeded (6) 80 | - 942432 [+3/PL4] - Restricted SQL Character Anomaly Detection (args): # of special characters exceeded (2) 81 | + 949110 [+0/PL1] - Inbound Anomaly Score Exceeded (Total Score: 43) 82 | 83 | Total Score (from matched rules): 20 84 | ``` 85 | 86 | `+` rules are the ones that are actually matched, while `-` rules apply to different paranoia levels (`1` by default) 87 | 88 | You can set a specific Paranoia Level to calculate the score (the default PL is `1`) 89 | 90 | ```console 91 | $ python3 main.py "" -v -PL 2 92 | 93 | GET http://www.modsecurity.org/test?q=%3Cscript%3Ealert%281%29%3C%2Fscript%3E 94 | 95 | # Matched rules 96 | 97 | + 920320 [+2/PL2] - Missing User Agent Header 98 | - 920273 [+5/PL4] - Invalid character in request (outside of very strict set) 99 | + 941100 [+5/PL1] - XSS Attack Detected via libinjection 100 | + 941110 [+5/PL1] - XSS Filter - Category 1: Script Tag Vector 101 | + 941160 [+5/PL1] - NoScript XSS InjectionChecker: HTML Injection 102 | + 941390 [+5/PL1] - Javascript method detected 103 | + 941320 [+5/PL2] - Possible XSS Attack Detected - HTML Tag Handler 104 | + 942131 [+5/PL2] - SQL Injection Attack: SQL Boolean-based attack detected 105 | - 942431 [+3/PL3] - Restricted SQL Character Anomaly Detection (args): # of special characters exceeded (6) 106 | - 942432 [+3/PL4] - Restricted SQL Character Anomaly Detection (args): # of special characters exceeded (2) 107 | + 949110 [+0/PL1] - Inbound Anomaly Score Exceeded (Total Score: 43) 108 | 109 | Total Score (from matched rules): 32 110 | ``` 111 | 112 | WARNING! You can see that it prints two `Total Scores`. One (`32`) is calculated by matched rules (`+`), while the other one (`43`) is the error message from rule `949110` (which is calculated at Paranoia Level 4). 113 | 114 | If you don't want this message, you can either increase the Anomaly Score threshold via your `conf/crs-setup.conf`, or by removing rule `949110` file entirely (not advised). 115 | 116 | `modsecurity-cli` has many options, check out the `--help` for all details! *(a wiki is coming)* 117 | 118 | ```console 119 | $ python3 main.py --help 120 | ``` 121 | 122 | ## TODOs 123 | 124 | This CLI wrapper is still under development, so you might not find some features that are interesting to you just yet. 125 | 126 | Here's the list of our current and future steps: 127 | 128 | - [x] ModSecurity from CLI 129 | - [x] Import all rules in a folder 130 | - [x] Support GET parameters evaluation 131 | - [x] Support Request Header evaluation 132 | - [x] Set default config to avoid matching [rule 901001](https://github.com/coreruleset/coreruleset/blob/v4.0/dev/rules/REQUEST-901-INITIALIZATION.conf#L54-L63) 133 | - [x] Score based on Paranoia Level (basic config uses PL/4, then we filter on a given PL - default: 1) 134 | - [x] Support POST request evaluation 135 | - [ ] Full URI evaluation 136 | - [ ] Create Python package 137 | - [ ] Wiki to fully document every option 138 | - [ ] Integration with [regrets](https://github.com/AvalZ/regrets) 139 | - [ ] Response evaluation (currently supports requests only) 140 | 141 | If you want to contribute by adding something from the list, PRs are welcome :sunglasses: 142 | 143 | ## Contributors 144 | 145 | - Andrea Valenza ([AvalZ](https://github.com/avalz)) - avalenza89@gmail.com 146 | - Luca Demetrio ([zangobot](https://github.com/zangobot)) - luca.demetrio@dibris.unige.it 147 | 148 | -------------------------------------------------------------------------------- /conf/crs-setup.conf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------------------ 2 | # OWASP ModSecurity Core Rule Set ver.4.0.0-rc2 3 | # Copyright (c) 2006-2020 Trustwave and contributors. All rights reserved. 4 | # Copyright (c) 2021-2023 Core Rule Set project. All rights reserved. 5 | # 6 | # The OWASP ModSecurity Core Rule Set is distributed under 7 | # Apache Software License (ASL) version 2 8 | # Please see the enclosed LICENSE file for full details. 9 | # ------------------------------------------------------------------------ 10 | 11 | 12 | # 13 | # -- [[ Introduction ]] -------------------------------------------------------- 14 | # 15 | # The OWASP ModSecurity Core Rule Set (CRS) is a set of generic attack 16 | # detection rules that provide a base level of protection for any web 17 | # application. They are written for the open source, cross-platform 18 | # ModSecurity Web Application Firewall. 19 | # 20 | # See also: 21 | # https://coreruleset.org/ 22 | # https://github.com/coreruleset/coreruleset 23 | # https://owasp.org/www-project-modsecurity-core-rule-set/ 24 | # 25 | 26 | 27 | # 28 | # -- [[ System Requirements ]] ------------------------------------------------- 29 | # 30 | # CRS requires ModSecurity version 2.8.0 or above. 31 | # We recommend to always use the newest ModSecurity version. 32 | # 33 | # The configuration directives/settings in this file are used to control 34 | # the OWASP ModSecurity CRS. These settings do **NOT** configure the main 35 | # ModSecurity settings (modsecurity.conf) such as SecRuleEngine, 36 | # SecRequestBodyAccess, SecAuditEngine, SecDebugLog, and XML processing. 37 | # 38 | # The CRS assumes that modsecurity.conf has been loaded. It is bundled with 39 | # ModSecurity. If you don't have it, you can get it from: 40 | # 2.x: https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v2/master/modsecurity.conf-recommended 41 | # 3.x: https://raw.githubusercontent.com/SpiderLabs/ModSecurity/v3/master/modsecurity.conf-recommended 42 | # 43 | # The order of file inclusion in your webserver configuration should always be: 44 | # 1. modsecurity.conf 45 | # 2. crs-setup.conf (this file) 46 | # 3. rules/*.conf (the CRS rule files) 47 | # 48 | # Please refer to the INSTALL file for detailed installation instructions. 49 | # 50 | 51 | 52 | # 53 | # -- [[ Mode of Operation: Anomaly Scoring vs. Self-Contained ]] --------------- 54 | # 55 | # The CRS can run in two modes: 56 | # 57 | # -- [[ Anomaly Scoring Mode (default) ]] -- 58 | # In CRS3, anomaly mode is the default and recommended mode, since it gives the 59 | # most accurate log information and offers the most flexibility in setting your 60 | # blocking policies. It is also called "collaborative detection mode". 61 | # In this mode, each matching rule increases an 'anomaly score'. 62 | # At the conclusion of the inbound rules, and again at the conclusion of the 63 | # outbound rules, the anomaly score is checked, and the blocking evaluation 64 | # rules apply a disruptive action, by default returning an error 403. 65 | # 66 | # -- [[ Self-Contained Mode ]] -- 67 | # In this mode, rules apply an action instantly. This was the CRS2 default. 68 | # It can lower resource usage, at the cost of less flexibility in blocking policy 69 | # and less informative audit logs (only the first detected threat is logged). 70 | # Rules inherit the disruptive action that you specify (i.e. deny, drop, etc). 71 | # The first rule that matches will execute this action. In most cases this will 72 | # cause evaluation to stop after the first rule has matched, similar to how many 73 | # IDSs function. 74 | # 75 | # -- [[ Alert Logging Control ]] -- 76 | # In the mode configuration, you must also adjust the desired logging options. 77 | # There are three common options for dealing with logging. By default CRS enables 78 | # logging to the webserver error log (or Event viewer) plus detailed logging to 79 | # the ModSecurity audit log (configured under SecAuditLog in modsecurity.conf). 80 | # 81 | # - To log to both error log and ModSecurity audit log file, use: "log,auditlog" 82 | # - To log *only* to the ModSecurity audit log file, use: "nolog,auditlog" 83 | # - To log *only* to the error log file, use: "log,noauditlog" 84 | # 85 | # Examples for the various modes follow. 86 | # You must leave one of the following options enabled. 87 | # Note that you must specify the same line for phase:1 and phase:2. 88 | # 89 | 90 | # Default: Anomaly Scoring mode, log to error log, log to ModSecurity audit log 91 | # - By default, offending requests are blocked with an error 403 response. 92 | # - To change the disruptive action, see RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example 93 | # and review section 'Changing the Disruptive Action for Anomaly Mode'. 94 | # - In Apache, you can use ErrorDocument to show a friendly error page or 95 | # perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html 96 | # 97 | SecDefaultAction "phase:1,log,auditlog,pass" 98 | SecDefaultAction "phase:2,log,auditlog,pass" 99 | 100 | # Example: Anomaly Scoring mode, log only to ModSecurity audit log 101 | # - By default, offending requests are blocked with an error 403 response. 102 | # - To change the disruptive action, see RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf.example 103 | # and review section 'Changing the Disruptive Action for Anomaly Mode'. 104 | # - In Apache, you can use ErrorDocument to show a friendly error page or 105 | # perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html 106 | # 107 | # SecDefaultAction "phase:1,nolog,auditlog,pass" 108 | # SecDefaultAction "phase:2,nolog,auditlog,pass" 109 | 110 | # Example: Self-contained mode, return error 403 on blocking 111 | # - In this configuration the default disruptive action becomes 'deny'. After a 112 | # rule triggers, it will stop processing the request and return an error 403. 113 | # - You can also use a different error status, such as 404, 406, et cetera. 114 | # - In Apache, you can use ErrorDocument to show a friendly error page or 115 | # perform a redirect: https://httpd.apache.org/docs/2.4/custom-error.html 116 | # 117 | # SecDefaultAction "phase:1,log,auditlog,deny,status:403" 118 | # SecDefaultAction "phase:2,log,auditlog,deny,status:403" 119 | 120 | # Example: Self-contained mode, redirect back to homepage on blocking 121 | # - In this configuration the 'tag' action includes the Host header data in the 122 | # log. This helps to identify which virtual host triggered the rule (if any). 123 | # - Note that this might cause redirect loops in some situations; for example 124 | # if a Cookie or User-Agent header is blocked, it will also be blocked when 125 | # the client subsequently tries to access the homepage. You can also redirect 126 | # to another custom URL. 127 | # SecDefaultAction "phase:1,log,auditlog,redirect:'http://%{request_headers.host}/',tag:'Host: %{request_headers.host}'" 128 | # SecDefaultAction "phase:2,log,auditlog,redirect:'http://%{request_headers.host}/',tag:'Host: %{request_headers.host}'" 129 | 130 | 131 | # 132 | # -- [[ Paranoia Level Initialization ]] --------------------------------------- 133 | # 134 | # The Paranoia Level (PL) setting allows you to choose the desired level 135 | # of rule checks that will add to your anomaly scores. 136 | # 137 | # With each paranoia level increase, the CRS enables additional rules 138 | # giving you a higher level of security. However, higher paranoia levels 139 | # also increase the possibility of blocking some legitimate traffic due to 140 | # false alarms (also named false positives or FPs). If you use higher 141 | # paranoia levels, it is likely that you will need to add some exclusion 142 | # rules for certain requests and applications receiving complex input. 143 | # 144 | # - A paranoia level of 1 is default. In this level, most core rules 145 | # are enabled. PL1 is advised for beginners, installations 146 | # covering many different sites and applications, and for setups 147 | # with standard security requirements. 148 | # At PL1 you should face FPs rarely. If you encounter FPs, please 149 | # open an issue on the CRS GitHub site and don't forget to attach your 150 | # complete Audit Log record for the request with the issue. 151 | # - Paranoia level 2 includes many extra rules, for instance enabling 152 | # many regexp-based SQL and XSS injection protections, and adding 153 | # extra keywords checked for code injections. PL2 is advised 154 | # for moderate to experienced users desiring more complete coverage 155 | # and for installations with elevated security requirements. 156 | # PL2 comes with some FPs which you need to handle. 157 | # - Paranoia level 3 enables more rules and keyword lists, and tweaks 158 | # limits on special characters used. PL3 is aimed at users experienced 159 | # at the handling of FPs and at installations with a high security 160 | # requirement. 161 | # - Paranoia level 4 further restricts special characters. 162 | # The highest level is advised for experienced users protecting 163 | # installations with very high security requirements. Running PL4 will 164 | # likely produce a very high number of FPs which have to be 165 | # treated before the site can go productive. 166 | # 167 | # All rules will log their PL to the audit log; 168 | # example: [tag "paranoia-level/2"]. This allows you to deduct from the 169 | # audit log how the WAF behavior is affected by paranoia level. 170 | # 171 | # It is important to also look into the variable 172 | # tx.enforce_bodyproc_urlencoded (Enforce Body Processor URLENCODED) 173 | # defined below. Enabling it closes a possible bypass of CRS. 174 | # 175 | # Uncomment this rule to change the default: 176 | # 177 | SecAction \ 178 | "id:900000,\ 179 | phase:1,\ 180 | pass,\ 181 | t:none,\ 182 | nolog,\ 183 | setvar:tx.blocking_paranoia_level=4" 184 | 185 | 186 | # It is possible to execute rules from a higher paranoia level but not include 187 | # them in the anomaly scoring. This allows you to take a well-tuned system on 188 | # paranoia level 1 and add rules from paranoia level 2 without having to fear 189 | # the new rules would lead to false positives that raise your score above the 190 | # threshold. 191 | # This optional feature is enabled by uncommenting the following rule and 192 | # setting the tx.detection_paranoia_level. 193 | # Technically, rules up to the level defined in tx.detection_paranoia_level 194 | # will be executed, but only the rules up to tx.blocking_paranoia_level affect the 195 | # anomaly scores. 196 | # By default, tx.detection_paranoia_level is set to tx.blocking_paranoia_level. 197 | # tx.detection_paranoia_level must not be lower than tx.blocking_paranoia_level. 198 | # 199 | # Please notice that setting tx.detection_paranoia_level to a higher paranoia 200 | # level results in a performance impact that is equally high as setting 201 | # tx.blocking_paranoia_level to said level. 202 | # 203 | #SecAction \ 204 | # "id:900001,\ 205 | # phase:1,\ 206 | # pass,\ 207 | # t:none,\ 208 | # nolog,\ 209 | # setvar:tx.detection_paranoia_level=1" 210 | 211 | 212 | # 213 | # -- [[ Enforce Body Processor URLENCODED ]] ----------------------------------- 214 | # 215 | # ModSecurity selects the body processor based on the Content-Type request 216 | # header. But clients are not always setting the Content-Type header for their 217 | # request body payloads. This will leave ModSecurity with limited vision into 218 | # the payload. The variable tx.enforce_bodyproc_urlencoded lets you force the 219 | # URLENCODED body processor in these situations. This is off by default, as it 220 | # implies a change of the behaviour of ModSecurity beyond CRS (the body 221 | # processor applies to all rules, not only CRS) and because it may lead to 222 | # false positives already on paranoia level 1. However, enabling this variable 223 | # closes a possible bypass of CRS so it should be considered. 224 | # 225 | # Uncomment this rule to change the default: 226 | # 227 | #SecAction \ 228 | # "id:900010,\ 229 | # phase:1,\ 230 | # pass,\ 231 | # t:none,\ 232 | # nolog,\ 233 | # setvar:tx.enforce_bodyproc_urlencoded=1" 234 | 235 | 236 | # 237 | # -- [[ Anomaly Scoring Mode Severity Levels ]] -------------------------------- 238 | # 239 | # Each rule in the CRS has an associated severity level. 240 | # These are the default scoring points for each severity level. 241 | # These settings will be used to increment the anomaly score if a rule matches. 242 | # You may adjust these points to your liking, but this is usually not needed. 243 | # 244 | # - CRITICAL severity: Anomaly Score of 5. 245 | # Mostly generated by the application attack rules (93x and 94x files). 246 | # - ERROR severity: Anomaly Score of 4. 247 | # Generated mostly from outbound leakage rules (95x files). 248 | # - WARNING severity: Anomaly Score of 3. 249 | # Generated mostly by malicious client rules (91x files). 250 | # - NOTICE severity: Anomaly Score of 2. 251 | # Generated mostly by the protocol rules (92x files). 252 | # 253 | # In anomaly mode, these scores are cumulative. 254 | # So it's possible for a request to hit multiple rules. 255 | # 256 | # (Note: In this file, we use 'phase:1' to set CRS configuration variables. 257 | # In general, 'phase:request' is used. However, we want to make absolutely sure 258 | # that all configuration variables are set before the CRS rules are processed.) 259 | # 260 | #SecAction \ 261 | # "id:900100,\ 262 | # phase:1,\ 263 | # pass,\ 264 | # t:none,\ 265 | # nolog,\ 266 | # setvar:tx.critical_anomaly_score=5,\ 267 | # setvar:tx.error_anomaly_score=4,\ 268 | # setvar:tx.warning_anomaly_score=3,\ 269 | # setvar:tx.notice_anomaly_score=2" 270 | 271 | 272 | # 273 | # -- [[ Anomaly Scoring Mode Blocking Threshold Levels ]] ---------------------- 274 | # 275 | # Here, you can specify at which cumulative anomaly score an inbound request, 276 | # or outbound response, gets blocked. 277 | # 278 | # Most detected inbound threats will give a critical score of 5. 279 | # Smaller violations, like violations of protocol/standards, carry lower scores. 280 | # 281 | # [ At default value ] 282 | # If you keep the blocking thresholds at the defaults, the CRS will work 283 | # similarly to previous CRS versions: a single critical rule match will cause 284 | # the request to be blocked and logged. 285 | # 286 | # [ Using higher values ] 287 | # If you want to make the CRS less sensitive, you can increase the blocking 288 | # thresholds, for instance to 7 (which would require multiple rule matches 289 | # before blocking) or 10 (which would require at least two critical alerts - or 290 | # a combination of many lesser alerts), or even higher. However, increasing the 291 | # thresholds might cause some attacks to bypass the CRS rules or your policies. 292 | # 293 | # [ New deployment strategy: Starting high and decreasing ] 294 | # It is a common practice to start a fresh CRS installation with elevated 295 | # anomaly scoring thresholds (>100) and then lower the limits as your 296 | # confidence in the setup grows. You may also look into the Sampling 297 | # Percentage section below for a different strategy to ease into a new 298 | # CRS installation. 299 | # 300 | # [ Anomaly Threshold / Paranoia Level Quadrant ] 301 | # 302 | # High Anomaly Limit | High Anomaly Limit 303 | # Low Paranoia Level | High Paranoia Level 304 | # -> Fresh Site | -> Experimental Site 305 | # ------------------------------------------------------ 306 | # Low Anomaly Limit | Low Anomaly Limit 307 | # Low Paranoia Level | High Paranoia Level 308 | # -> Standard Site | -> High Security Site 309 | # 310 | # Uncomment this rule to change the defaults: 311 | # 312 | #SecAction \ 313 | # "id:900110,\ 314 | # phase:1,\ 315 | # pass,\ 316 | # t:none,\ 317 | # nolog,\ 318 | # setvar:tx.inbound_anomaly_score_threshold=5,\ 319 | # setvar:tx.outbound_anomaly_score_threshold=4" 320 | 321 | 322 | # 323 | # -- [[ Application Specific Rule Exclusions ]] -------------------------------- 324 | # 325 | # CRS 3.x contained exclusion packages to tweak the CRS for use with common 326 | # web applications, lowering the number of false positives. 327 | # 328 | # In CRS 4, these are no longer part of the CRS itself, but they are available 329 | # as "CRS plugins". Some plugins improve support for web applications, and others 330 | # may bring new functionality. Plugins are not installed by default, but can be 331 | # downloaded from the plugin registry: 332 | # https://github.com/coreruleset/plugin-registry 333 | # 334 | # For detailed information about using and installing plugins, please see: 335 | # https://coreruleset.org/docs/concepts/plugins/ 336 | 337 | 338 | # 339 | # -- [[ Anomaly Score Reporting Level ]] --------------------------------------- 340 | # 341 | # When a request is blocked due to the anomaly score meeting or exceeding the 342 | # anomaly threshold then the blocking rule will also report the anomaly score. 343 | # This applies to the separate inbound and outbound anomaly scores. 344 | # 345 | # In phase 5, there are additional rules that can perform additional reporting 346 | # of anomaly scores with a verbosity that depends on the reporting level defined 347 | # below. 348 | # 349 | # By setting the reporting level you control whether you want additional 350 | # reporting beyond the blocking rule or not and, if yes, which requests should 351 | # be covered. The higher the reporting level, the more verbose the reporting is. 352 | # 353 | # There are 6 reporting levels: 354 | # 355 | # 0 - Reporting disabled 356 | # 1 - Reporting for requests with a blocking anomaly score >= a threshold 357 | # 2 - Reporting for requests with a detection anomaly score >= a threshold 358 | # 3 - Reporting for requests with a blocking anomaly score greater than 0 359 | # 4 - Reporting for requests with a detection anomaly score greater than 0 360 | # 5 - Reporting for all requests 361 | # 362 | # Note: Reporting levels 1 and 2 make it possible to differentiate between 363 | # requests that are blocked and requests that are *not* blocked but would have 364 | # been blocked if the blocking PL was equal to detection PL. This may be useful 365 | # for certain FP tuning methodologies, for example moving to a higher PL. 366 | # 367 | # A value of 5 can be useful on platforms where you are interested in logging 368 | # non-scoring requests, yet it is not possible to report this information in 369 | # the request/access log. This applies to Nginx, for example. 370 | # 371 | #SecAction \ 372 | # "id:900115,\ 373 | # phase:1,\ 374 | # pass,\ 375 | # t:none,\ 376 | # nolog,\ 377 | # setvar:tx.reporting_level=4" 378 | 379 | 380 | # 381 | # -- [[ Early Anomaly Scoring Mode Blocking ]] ------------------------------ 382 | # 383 | # The anomaly scores for the request and the responses are generally summed up 384 | # and evaluated at the end of phase:2 and at the end of phase:4 respectively. 385 | # However, it is possible to enable an early evaluation of these anomaly scores 386 | # at the end of phase:1 and at the end of phase:3. 387 | # 388 | # If a request (or a response) hits the anomaly threshold in this early 389 | # evaluation, then blocking happens immediately (if blocking is enabled) and 390 | # the phase 2 (and phase 4 respectively) will no longer be executed. 391 | # 392 | # Enable the rule 900120 that sets the variable tx.early_blocking to 1 in order 393 | # to enable early blocking. The variable tx.early_blocking is set to 0 by 394 | # default. Early blocking is thus disabled by default. 395 | # 396 | # Please note that early blocking will hide potential alerts from you. This 397 | # means that a payload that would appear in an alert in phase 2 (or phase 4) 398 | # does not get evaluated if the request is being blocked early. So when you 399 | # disabled early blocking again at some point in the future, then new alerts 400 | # from phase 2 might pop up. 401 | #SecAction \ 402 | # "id:900120,\ 403 | # phase:1,\ 404 | # pass,\ 405 | # t:none,\ 406 | # nolog,\ 407 | # setvar:tx.early_blocking=1" 408 | 409 | 410 | # 411 | # -- [[ Initialize Default Collections ]] ----------------------------------- 412 | # 413 | # CRS provides a centralized option to initialize and populate collections 414 | # meant to be used by plugins (E.g.DoS protection plugin). 415 | # By default, Global and IP collections (see rule 901320), 416 | # being not used by core rules, are not initialized. 417 | # 418 | # Uncomment this rule to change the default: 419 | # 420 | #SecAction \ 421 | # "id:900130,\ 422 | # phase:1,\ 423 | # pass,\ 424 | # t:none,\ 425 | # nolog,\ 426 | # setvar:tx.enable_default_collections=1" 427 | 428 | 429 | # 430 | # -- [[ HTTP Policy Settings ]] ------------------------------------------------ 431 | # 432 | # This section defines your policies for the HTTP protocol, such as: 433 | # - allowed HTTP versions, HTTP methods, allowed request Content-Types 434 | # - forbidden file extensions (e.g. .bak, .sql) and request headers (e.g. Proxy) 435 | # 436 | # These variables are used in the following rule files: 437 | # - REQUEST-911-METHOD-ENFORCEMENT.conf 438 | # - REQUEST-920-PROTOCOL-ENFORCEMENT.conf 439 | 440 | # HTTP methods that a client is allowed to use. 441 | # Default: GET HEAD POST OPTIONS 442 | # Example: for RESTful APIs, add the following methods: PUT PATCH DELETE 443 | # Example: for WebDAV, add the following methods: CHECKOUT COPY DELETE LOCK 444 | # MERGE MKACTIVITY MKCOL MOVE PROPFIND PROPPATCH PUT UNLOCK 445 | # Uncomment this rule to change the default. 446 | #SecAction \ 447 | # "id:900200,\ 448 | # phase:1,\ 449 | # pass,\ 450 | # t:none,\ 451 | # nolog,\ 452 | # setvar:'tx.allowed_methods=GET HEAD POST OPTIONS'" 453 | 454 | # Content-Types that a client is allowed to send in a request. 455 | # Default: |application/x-www-form-urlencoded| |multipart/form-data| |multipart/related| 456 | # |text/xml| |application/xml| |application/soap+xml| |application/json| 457 | # |application/cloudevents+json| |application/cloudevents-batch+json| 458 | # 459 | # Please note, that the rule where CRS uses this variable (920420) evaluates it with operator 460 | # `@within`, which is case sensitive, but uses t:lowercase. You must add your whole custom 461 | # Content-Type with lowercase. 462 | # 463 | # Bypass Warning: some applications may not rely on the content-type request header in order 464 | # to parse the request body. This could make an attacker able to send malicious URLENCODED/JSON/XML 465 | # payloads without being detected by the WAF. Allowing request content-type that doesn't activate any 466 | # body processor (for example: "text/plain", "application/x-amf", "application/octet-stream", etc..) 467 | # could lead to a WAF bypass. For example, a malicious JSON payload submitted with a "text/plain" 468 | # content type may still be interpreted as JSON by a backend application but would not trigger the 469 | # JSON body parser at the WAF, leading to a bypass. 470 | # 471 | # To prevent blocking request with not allowed content-type by default, you can create an exclusion 472 | # rule that removes rule 920420. For example: 473 | #SecRule REQUEST_HEADERS:Content-Type "@rx ^text/plain" \ 474 | # "id:1234,\ 475 | # phase:1,\ 476 | # pass,\ 477 | # t:none,\ 478 | # nolog,\ 479 | # ctl:ruleRemoveById=920420,\ 480 | # chain" 481 | # SecRule REQUEST_URI "@rx ^/foo/bar" \ 482 | # "t:none" 483 | # 484 | # Uncomment this rule to change the default. 485 | # 486 | #SecAction \ 487 | # "id:900220,\ 488 | # phase:1,\ 489 | # pass,\ 490 | # t:none,\ 491 | # nolog,\ 492 | # setvar:'tx.allowed_request_content_type=|application/x-www-form-urlencoded| |multipart/form-data| |multipart/related| |text/xml| |application/xml| |application/soap+xml| |application/json| |application/cloudevents+json| |application/cloudevents-batch+json|'" 493 | 494 | # Allowed HTTP versions. 495 | # Default: HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0 HTTP/3 HTTP/3.0 496 | # Example for legacy clients: HTTP/0.9 HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0 HTTP/3 HTTP/3.0 497 | # Note that some web server versions use 'HTTP/2', some 'HTTP/2.0', so 498 | # we include both version strings by default. 499 | # Uncomment this rule to change the default. 500 | #SecAction \ 501 | # "id:900230,\ 502 | # phase:1,\ 503 | # pass,\ 504 | # t:none,\ 505 | # nolog,\ 506 | # setvar:'tx.allowed_http_versions=HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/2.0 HTTP/3 HTTP/3.0'" 507 | 508 | # Forbidden file extensions. 509 | # Guards against unintended exposure of development/configuration files. 510 | # Default: .asa/ .asax/ .ascx/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .rdb/ .resources/ .resx/ .sql/ .swp/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/ 511 | # Example: .bak/ .config/ .conf/ .db/ .ini/ .log/ .old/ .pass/ .pdb/ .rdb/ .sql/ 512 | # Note that .axd was removed due to false positives (see PR 1925). 513 | # 514 | # To additionally guard against configuration/install archive files from being 515 | # accidentally exposed, common archive file extensions can be added to the 516 | # restricted extensions list. An example list of common archive file extensions 517 | # is presented below: 518 | # .7z/ .br/ .bz/ .bz2/ .cab/ .cpio/ .gz/ .img/ .iso/ .jar/ .rar/ .tar/ .tbz2/ .tgz/ .txz/ .xz/ .zip/ .zst/ 519 | # (Source: https://en.wikipedia.org/wiki/List_of_archive_formats) 520 | # 521 | # Uncomment this rule to change the default. 522 | #SecAction \ 523 | # "id:900240,\ 524 | # phase:1,\ 525 | # pass,\ 526 | # t:none,\ 527 | # nolog,\ 528 | # setvar:'tx.restricted_extensions=.asa/ .asax/ .ascx/ .backup/ .bak/ .bat/ .cdx/ .cer/ .cfg/ .cmd/ .com/ .config/ .conf/ .cs/ .csproj/ .csr/ .dat/ .db/ .dbf/ .dll/ .dos/ .htr/ .htw/ .ida/ .idc/ .idq/ .inc/ .ini/ .key/ .licx/ .lnk/ .log/ .mdb/ .old/ .pass/ .pdb/ .pol/ .printer/ .pwd/ .rdb/ .resources/ .resx/ .sql/ .swp/ .sys/ .vb/ .vbs/ .vbproj/ .vsdisco/ .webinfo/ .xsd/ .xsx/'" 529 | 530 | # Restricted request headers. 531 | # The HTTP request headers that CRS restricts are split into two categories: 532 | # basic (always forbidden) and extended (may be forbidden). All header names 533 | # should be lowercase and enclosed by /slashes/ as delimiters. 534 | # 535 | # [ Basic ] 536 | # Includes deprecated headers and headers with known security risks. Always 537 | # forbidden. 538 | # Default: /content-encoding/ /proxy/ /lock-token/ /content-range/ /if/ /x-http-method-override/ /x-http-method/ /x-method-override/ 539 | # 540 | # /content-encoding/ 541 | # Used to list any encodings that have been applied to the original payload. 542 | # Only used for compression, which isn't supported by CRS by default since CRS 543 | # blocks newlines and null bytes inside the request body. Most compression 544 | # algorithms require at least null bytes per RFC. Blocking Content-Encoding 545 | # shouldn't break anything and increases security since WAF engines, including 546 | # ModSecurity, are typically incapable of properly scanning compressed request 547 | # bodies. 548 | # 549 | # /proxy/ 550 | # Blocking this prevents the 'httpoxy' vulnerability: https://httpoxy.org 551 | # 552 | # /lock-token/ 553 | # 554 | # /content-range/ 555 | # 556 | # /if/ 557 | # 558 | # /x-http-method-override/ 559 | # /x-http-method/ 560 | # /x-method-override/ 561 | # Blocking these headers prevents method override attacks, as described here: 562 | # https://www.sidechannel.blog/en/http-method-override-what-it-is-and-how-a-pentester-can-use-it 563 | # 564 | # Uncomment this rule to change the default. 565 | #SecAction \ 566 | # "id:900250,\ 567 | # phase:1,\ 568 | # pass,\ 569 | # t:none,\ 570 | # nolog,\ 571 | # setvar:'tx.restricted_headers_basic=/content-encoding/ /proxy/ /lock-token/ /content-range/ /if/ /x-http-method-override/ /x-http-method/ /x-method-override/'" 572 | # 573 | # [ Extended ] 574 | # Includes deprecated headers that are still in use (so false positives are 575 | # possible) and headers with possible security risks. Forbidden at a higher 576 | # paranoia level. 577 | # Default: /accept-charset/ 578 | # 579 | # /accept-charset/ 580 | # Deprecated header that should not be used by clients and should be ignored 581 | # by servers. Can be used for a response WAF bypass by asking for a charset 582 | # that the WAF cannot decode. Considered to be a good indicator of suspicious 583 | # behavior but produces too many false positives to be forbidden by default. 584 | # References: 585 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Charset 586 | # https://github.com/coreruleset/coreruleset/issues/3140 587 | # 588 | # Uncomment this rule to change the default. 589 | #SecAction \ 590 | # "id:900255,\ 591 | # phase:1,\ 592 | # pass,\ 593 | # t:none,\ 594 | # nolog,\ 595 | # setvar:'tx.restricted_headers_extended=/accept-charset/'" 596 | 597 | # Content-Types charsets that a client is allowed to send in a request. 598 | # The content-types are enclosed by |pipes| as delimiters to guarantee exact matches. 599 | # Default: |utf-8| |iso-8859-1| |iso-8859-15| |windows-1252| 600 | # Uncomment this rule to change the default. 601 | #SecAction \ 602 | # "id:900280,\ 603 | # phase:1,\ 604 | # pass,\ 605 | # t:none,\ 606 | # nolog,\ 607 | # setvar:'tx.allowed_request_content_type_charset=|utf-8| |iso-8859-1| |iso-8859-15| |windows-1252|'" 608 | 609 | # 610 | # -- [[ HTTP Argument/Upload Limits ]] ----------------------------------------- 611 | # 612 | # Here you can define optional limits on HTTP get/post parameters and uploads. 613 | # This can help to prevent application specific DoS attacks. 614 | # 615 | # These values are checked in REQUEST-920-PROTOCOL-ENFORCEMENT.conf. 616 | # Beware of blocking legitimate traffic when enabling these limits. 617 | # 618 | 619 | # Block request if number of arguments is too high 620 | # Default: unlimited 621 | # Example: 255 622 | # Uncomment this rule to set a limit. 623 | #SecAction \ 624 | # "id:900300,\ 625 | # phase:1,\ 626 | # pass,\ 627 | # t:none,\ 628 | # nolog,\ 629 | # setvar:tx.max_num_args=255" 630 | 631 | # Block request if the length of any argument name is too high 632 | # Default: unlimited 633 | # Example: 100 634 | # Uncomment this rule to set a limit. 635 | #SecAction \ 636 | # "id:900310,\ 637 | # phase:1,\ 638 | # pass,\ 639 | # t:none,\ 640 | # nolog,\ 641 | # setvar:tx.arg_name_length=100" 642 | 643 | # Block request if the length of any argument value is too high 644 | # Default: unlimited 645 | # Example: 400 646 | # Uncomment this rule to set a limit. 647 | #SecAction \ 648 | # "id:900320,\ 649 | # phase:1,\ 650 | # pass,\ 651 | # t:none,\ 652 | # nolog,\ 653 | # setvar:tx.arg_length=400" 654 | 655 | # Block request if the total length of all combined arguments is too high 656 | # Default: unlimited 657 | # Example: 64000 658 | # Uncomment this rule to set a limit. 659 | #SecAction \ 660 | # "id:900330,\ 661 | # phase:1,\ 662 | # pass,\ 663 | # t:none,\ 664 | # nolog,\ 665 | # setvar:tx.total_arg_length=64000" 666 | 667 | # Block request if the file size of any individual uploaded file is too high 668 | # Default: unlimited 669 | # Example: 1048576 670 | # Uncomment this rule to set a limit. 671 | #SecAction \ 672 | # "id:900340,\ 673 | # phase:1,\ 674 | # pass,\ 675 | # t:none,\ 676 | # nolog,\ 677 | # setvar:tx.max_file_size=1048576" 678 | 679 | # Block request if the total size of all combined uploaded files is too high 680 | # Default: unlimited 681 | # Example: 1048576 682 | # Uncomment this rule to set a limit. 683 | #SecAction \ 684 | # "id:900350,\ 685 | # phase:1,\ 686 | # pass,\ 687 | # t:none,\ 688 | # nolog,\ 689 | # setvar:tx.combined_file_sizes=1048576" 690 | 691 | 692 | # 693 | # -- [[ Easing In / Sampling Percentage ]] ------------------------------------- 694 | # 695 | # Adding the Core Rule Set to an existing productive site can lead to false 696 | # positives, unexpected performance issues and other undesired side effects. 697 | # 698 | # It can be beneficial to test the water first by enabling the CRS for a 699 | # limited number of requests only and then, when you have solved the issues (if 700 | # any) and you have confidence in the setup, to raise the ratio of requests 701 | # being sent into the ruleset. 702 | # 703 | # Adjust the percentage of requests that are funnelled into the Core Rules by 704 | # setting TX.sampling_percentage below. The default is 100, meaning that every 705 | # request gets checked by the CRS. The selection of requests, which are going 706 | # to be checked, is based on a pseudo random number generated by ModSecurity. 707 | # 708 | # If a request is allowed to pass without being checked by the CRS, there is no 709 | # entry in the audit log (for performance reasons), but an error log entry is 710 | # written. If you want to disable the error log entry, then issue the 711 | # following directive somewhere after the inclusion of the CRS 712 | # (E.g., RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf). 713 | # 714 | #SecRuleUpdateActionById 901450 "nolog" 715 | # 716 | # ATTENTION: If this TX.sampling_percentage is below 100, then some of the 717 | # requests will bypass the Core Rules completely and you lose the ability to 718 | # protect your service with ModSecurity. 719 | # 720 | # Uncomment this rule to enable this feature: 721 | # 722 | #SecAction \ 723 | # "id:900400,\ 724 | # phase:1,\ 725 | # pass,\ 726 | # nolog,\ 727 | # setvar:tx.sampling_percentage=100" 728 | 729 | 730 | 731 | # 732 | # -- [[ Check UTF-8 encoding ]] ------------------------------------------------ 733 | # 734 | # The CRS can optionally check request contents for invalid UTF-8 encoding. 735 | # We only want to apply this check if UTF-8 encoding is actually used by the 736 | # site; otherwise it will result in false positives. 737 | # 738 | # Uncomment this rule to use this feature: 739 | # 740 | #SecAction \ 741 | # "id:900950,\ 742 | # phase:1,\ 743 | # pass,\ 744 | # t:none,\ 745 | # nolog,\ 746 | # setvar:tx.crs_validate_utf8_encoding=1" 747 | 748 | 749 | # 750 | # -- [[ Collection timeout ]] -------------------------------------------------- 751 | # 752 | # Set the SecCollectionTimeout directive from the ModSecurity default (1 hour) 753 | # to a lower setting which is appropriate to most sites. 754 | # This increases performance by cleaning out stale collection (block) entries. 755 | # 756 | # This value should be greater than or equal to any block durations or timeouts 757 | # set by plugins that make use of ModSecurity's persistent collections (e.g. the 758 | # DoS protection and IP reputation plugins). 759 | # 760 | # Ref: https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-(v2.x)#SecCollectionTimeout 761 | 762 | # Please keep this directive uncommented. 763 | # Default: 600 (10 minutes) 764 | SecCollectionTimeout 600 765 | 766 | 767 | # 768 | # -- [[ End of setup ]] -------------------------------------------------------- 769 | # 770 | # The CRS checks the tx.crs_setup_version variable to ensure that the setup 771 | # has been loaded. If you are not planning to use this setup template, 772 | # you must manually set the tx.crs_setup_version variable before including 773 | # the CRS rules/* files. 774 | # 775 | # The variable is a numerical representation of the CRS version number. 776 | # E.g., v3.0.0 is represented as 300. 777 | # 778 | SecAction \ 779 | "id:900990,\ 780 | phase:1,\ 781 | pass,\ 782 | t:none,\ 783 | nolog,\ 784 | setvar:tx.crs_setup_version=400" 785 | -------------------------------------------------------------------------------- /conf/modsecurity.conf: -------------------------------------------------------------------------------- 1 | # -- Rule engine initialization ---------------------------------------------- 2 | 3 | # Enable ModSecurity, attaching it to every transaction. Use detection 4 | # only to start with, because that minimises the chances of post-installation 5 | # disruption. 6 | # 7 | SecRuleEngine DetectionOnly 8 | 9 | 10 | # -- Request body handling --------------------------------------------------- 11 | 12 | # Allow ModSecurity to access request bodies. If you don't, ModSecurity 13 | # won't be able to see any POST parameters, which opens a large security 14 | # hole for attackers to exploit. 15 | # 16 | SecRequestBodyAccess On 17 | 18 | 19 | # Enable XML request body parser. 20 | # Initiate XML Processor in case of xml content-type 21 | # 22 | SecRule REQUEST_HEADERS:Content-Type "^(?:application(?:/soap\+|/)|text/)xml" \ 23 | "id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML" 24 | 25 | # Enable JSON request body parser. 26 | # Initiate JSON Processor in case of JSON content-type; change accordingly 27 | # if your application does not use 'application/json' 28 | # 29 | SecRule REQUEST_HEADERS:Content-Type "^application/json" \ 30 | "id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" 31 | 32 | # Sample rule to enable JSON request body parser for more subtypes. 33 | # Uncomment or adapt this rule if you want to engage the JSON 34 | # Processor for "+json" subtypes 35 | # 36 | #SecRule REQUEST_HEADERS:Content-Type "^application/[a-z0-9.-]+[+]json" \ 37 | # "id:'200006',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON" 38 | 39 | # Maximum request body size we will accept for buffering. If you support 40 | # file uploads then the value given on the first line has to be as large 41 | # as the largest file you are willing to accept. The second value refers 42 | # to the size of data, with files excluded. You want to keep that value as 43 | # low as practical. 44 | # 45 | SecRequestBodyLimit 13107200 46 | SecRequestBodyNoFilesLimit 131072 47 | 48 | # What to do if the request body size is above our configured limit. 49 | # Keep in mind that this setting will automatically be set to ProcessPartial 50 | # when SecRuleEngine is set to DetectionOnly mode in order to minimize 51 | # disruptions when initially deploying ModSecurity. 52 | # 53 | SecRequestBodyLimitAction Reject 54 | 55 | # Maximum parsing depth allowed for JSON objects. You want to keep this 56 | # value as low as practical. 57 | # 58 | SecRequestBodyJsonDepthLimit 512 59 | 60 | # Maximum number of args allowed per request. You want to keep this 61 | # value as low as practical. The value should match that in rule 200007. 62 | SecArgumentsLimit 1000 63 | 64 | # If SecArgumentsLimit has been set, you probably want to reject any 65 | # request body that has only been partly parsed. The value used in this 66 | # rule should match what was used with SecArgumentsLimit 67 | SecRule &ARGS "@ge 1000" \ 68 | "id:'200007', phase:2,t:none,log,deny,status:400,msg:'Failed to fully parse request body due to large argument count',severity:2" 69 | 70 | # Verify that we've correctly processed the request body. 71 | # As a rule of thumb, when failing to process a request body 72 | # you should reject the request (when deployed in blocking mode) 73 | # or log a high-severity alert (when deployed in detection-only mode). 74 | # 75 | SecRule REQBODY_ERROR "!@eq 0" \ 76 | "id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2" 77 | 78 | # By default be strict with what we accept in the multipart/form-data 79 | # request body. If the rule below proves to be too strict for your 80 | # environment consider changing it to detection-only. You are encouraged 81 | # _not_ to remove it altogether. 82 | # 83 | SecRule MULTIPART_STRICT_ERROR "!@eq 0" \ 84 | "id:'200003',phase:2,t:none,log,deny,status:400, \ 85 | msg:'Multipart request body failed strict validation: \ 86 | PE %{REQBODY_PROCESSOR_ERROR}, \ 87 | BQ %{MULTIPART_BOUNDARY_QUOTED}, \ 88 | BW %{MULTIPART_BOUNDARY_WHITESPACE}, \ 89 | DB %{MULTIPART_DATA_BEFORE}, \ 90 | DA %{MULTIPART_DATA_AFTER}, \ 91 | HF %{MULTIPART_HEADER_FOLDING}, \ 92 | LF %{MULTIPART_LF_LINE}, \ 93 | SM %{MULTIPART_MISSING_SEMICOLON}, \ 94 | IQ %{MULTIPART_INVALID_QUOTING}, \ 95 | IP %{MULTIPART_INVALID_PART}, \ 96 | IH %{MULTIPART_INVALID_HEADER_FOLDING}, \ 97 | FL %{MULTIPART_FILE_LIMIT_EXCEEDED}'" 98 | 99 | # Did we see anything that might be a boundary? 100 | # 101 | # Here is a short description about the ModSecurity Multipart parser: the 102 | # parser returns with value 0, if all "boundary-like" line matches with 103 | # the boundary string which given in MIME header. In any other cases it returns 104 | # with different value, eg. 1 or 2. 105 | # 106 | # The RFC 1341 descript the multipart content-type and its syntax must contains 107 | # only three mandatory lines (above the content): 108 | # * Content-Type: multipart/mixed; boundary=BOUNDARY_STRING 109 | # * --BOUNDARY_STRING 110 | # * --BOUNDARY_STRING-- 111 | # 112 | # First line indicates, that this is a multipart content, second shows that 113 | # here starts a part of the multipart content, third shows the end of content. 114 | # 115 | # If there are any other lines, which starts with "--", then it should be 116 | # another boundary id - or not. 117 | # 118 | # After 3.0.3, there are two kinds of types of boundary errors: strict and permissive. 119 | # 120 | # If multipart content contains the three necessary lines with correct order, but 121 | # there are one or more lines with "--", then parser returns with value 2 (non-zero). 122 | # 123 | # If some of the necessary lines (usually the start or end) misses, or the order 124 | # is wrong, then parser returns with value 1 (also a non-zero). 125 | # 126 | # You can choose, which one is what you need. The example below contains the 127 | # 'strict' mode, which means if there are any lines with start of "--", then 128 | # ModSecurity blocked the content. But the next, commented example contains 129 | # the 'permissive' mode, then you check only if the necessary lines exists in 130 | # correct order. Whit this, you can enable to upload PEM files (eg "----BEGIN.."), 131 | # or other text files, which contains eg. HTTP headers. 132 | # 133 | # The difference is only the operator - in strict mode (first) the content blocked 134 | # in case of any non-zero value. In permissive mode (second, commented) the 135 | # content blocked only if the value is explicit 1. If it 0 or 2, the content will 136 | # allowed. 137 | # 138 | 139 | # 140 | # See #1747 and #1924 for further information on the possible values for 141 | # MULTIPART_UNMATCHED_BOUNDARY. 142 | # 143 | SecRule MULTIPART_UNMATCHED_BOUNDARY "@eq 1" \ 144 | "id:'200004',phase:2,t:none,log,deny,msg:'Multipart parser detected a possible unmatched boundary.'" 145 | 146 | 147 | # PCRE Tuning 148 | # We want to avoid a potential RegEx DoS condition 149 | # 150 | SecPcreMatchLimit 1000 151 | SecPcreMatchLimitRecursion 1000 152 | 153 | # Some internal errors will set flags in TX and we will need to look for these. 154 | # All of these are prefixed with "MSC_". The following flags currently exist: 155 | # 156 | # MSC_PCRE_LIMITS_EXCEEDED: PCRE match limits were exceeded. 157 | # 158 | SecRule TX:/^MSC_/ "!@streq 0" \ 159 | "id:'200005',phase:2,t:none,deny,msg:'ModSecurity internal error flagged: %{MATCHED_VAR_NAME}'" 160 | 161 | 162 | # -- Response body handling -------------------------------------------------- 163 | 164 | # Allow ModSecurity to access response bodies. 165 | # You should have this directive enabled in order to identify errors 166 | # and data leakage issues. 167 | # 168 | # Do keep in mind that enabling this directive does increases both 169 | # memory consumption and response latency. 170 | # 171 | SecResponseBodyAccess On 172 | 173 | # Which response MIME types do you want to inspect? You should adjust the 174 | # configuration below to catch documents but avoid static files 175 | # (e.g., images and archives). 176 | # 177 | SecResponseBodyMimeType text/plain text/html text/xml 178 | 179 | # Buffer response bodies of up to 512 KB in length. 180 | SecResponseBodyLimit 524288 181 | 182 | # What happens when we encounter a response body larger than the configured 183 | # limit? By default, we process what we have and let the rest through. 184 | # That's somewhat less secure, but does not break any legitimate pages. 185 | # 186 | SecResponseBodyLimitAction ProcessPartial 187 | 188 | 189 | # -- Filesystem configuration ------------------------------------------------ 190 | 191 | # The location where ModSecurity stores temporary files (for example, when 192 | # it needs to handle a file upload that is larger than the configured limit). 193 | # 194 | # This default setting is chosen due to all systems have /tmp available however, 195 | # this is less than ideal. It is recommended that you specify a location that's private. 196 | # 197 | SecTmpDir /tmp/ 198 | 199 | # The location where ModSecurity will keep its persistent data. This default setting 200 | # is chosen due to all systems have /tmp available however, it 201 | # too should be updated to a place that other users can't access. 202 | # 203 | SecDataDir /tmp/ 204 | 205 | 206 | # -- File uploads handling configuration ------------------------------------- 207 | 208 | # The location where ModSecurity stores intercepted uploaded files. This 209 | # location must be private to ModSecurity. You don't want other users on 210 | # the server to access the files, do you? 211 | # 212 | #SecUploadDir /opt/modsecurity/var/upload/ 213 | 214 | # By default, only keep the files that were determined to be unusual 215 | # in some way (by an external inspection script). For this to work you 216 | # will also need at least one file inspection rule. 217 | # 218 | #SecUploadKeepFiles RelevantOnly 219 | 220 | # Uploaded files are by default created with permissions that do not allow 221 | # any other user to access them. You may need to relax that if you want to 222 | # interface ModSecurity to an external program (e.g., an anti-virus). 223 | # 224 | #SecUploadFileMode 0600 225 | 226 | 227 | # -- Debug log configuration ------------------------------------------------- 228 | 229 | # The default debug log configuration is to duplicate the error, warning 230 | # and notice messages from the error log. 231 | # 232 | #SecDebugLog /opt/modsecurity/var/log/debug.log 233 | #SecDebugLogLevel 3 234 | 235 | 236 | # -- Audit log configuration ------------------------------------------------- 237 | 238 | # Log the transactions that are marked by a rule, as well as those that 239 | # trigger a server error (determined by a 5xx or 4xx, excluding 404, 240 | # level response status codes). 241 | # 242 | SecAuditEngine RelevantOnly 243 | SecAuditLogRelevantStatus "^(?:5|4(?!04))" 244 | 245 | # Log everything we know about a transaction. 246 | SecAuditLogParts ABIJDEFHZ 247 | 248 | # Use a single file for logging. This is much easier to look at, but 249 | # assumes that you will use the audit log only ocassionally. 250 | # 251 | SecAuditLogType Serial 252 | SecAuditLog /var/log/modsec_audit.log 253 | 254 | # Specify the path for concurrent audit logging. 255 | #SecAuditLogStorageDir /opt/modsecurity/var/audit/ 256 | 257 | 258 | # -- Miscellaneous ----------------------------------------------------------- 259 | 260 | # Use the most commonly used application/x-www-form-urlencoded parameter 261 | # separator. There's probably only one application somewhere that uses 262 | # something else so don't expect to change this value. 263 | # 264 | SecArgumentSeparator & 265 | 266 | # Settle on version 0 (zero) cookies, as that is what most applications 267 | # use. Using an incorrect cookie version may open your installation to 268 | # evasion attacks (against the rules that examine named cookies). 269 | # 270 | SecCookieFormat 0 271 | 272 | # Specify your Unicode Code Point. 273 | # This mapping is used by the t:urlDecodeUni transformation function 274 | # to properly map encoded data to your language. Properly setting 275 | # these directives helps to reduce false positives and negatives. 276 | # 277 | SecUnicodeMapFile unicode.mapping 20127 278 | 279 | # Improve the quality of ModSecurity by sharing information about your 280 | # current ModSecurity version and dependencies versions. 281 | # The following information will be shared: ModSecurity version, 282 | # Web Server version, APR version, PCRE version, Lua version, Libxml2 283 | # version, Anonymous unique id for host. 284 | SecStatusEngine On 285 | 286 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from ModSecurity import ModSecurity 2 | from ModSecurity import RulesSet 3 | from ModSecurity import Transaction 4 | from ModSecurity import LogProperty 5 | 6 | import re 7 | import glob 8 | from urllib.parse import urlparse, urlencode 9 | import typer 10 | from typing import List, Optional 11 | from typing_extensions import Annotated 12 | from enum import Enum 13 | 14 | app = typer.Typer() 15 | 16 | class Severity(Enum): 17 | 18 | def __new__(cls, *args, **kwds): 19 | value = len(cls.__members__) 20 | obj = object.__new__(cls) 21 | obj._value_ = value 22 | return obj 23 | def __init__(self, severity_id, score): 24 | self.id = severity_id 25 | self.score = score 26 | 27 | EMERGENCY = 0, 0 # not used in CRS 28 | ALERT = 1, 0 # not used in CRS 29 | CRITICAL = 2, 5 30 | ERROR = 3, 4 31 | WARNING = 4, 3 32 | NOTICE = 5, 2 33 | INFO = 6, 0 # not used in CRS 34 | DEBUG = 7, 0 # not used in CRS 35 | 36 | def get_paranoia_level(rule): 37 | return next((int(tag.split('/')[1]) for tag in rule.m_tags if 'paranoia-level' in tag), 1) 38 | 39 | 40 | def version(value: bool): 41 | if value: 42 | modsec = ModSecurity() 43 | print(modsec.whoAmI()) 44 | exit() 45 | 46 | 47 | @app.command() 48 | def parameter( 49 | payloads: Annotated[List[str], typer.Argument()], 50 | keys: Annotated[List[str], typer.Option('-k', '--key', help="List of key for parameters (must match the number of payloads)")] = [], 51 | request_body: Annotated[typer.FileBinaryRead, typer.Option(help="Request Body file")] = None, 52 | base_uri: Annotated[str, typer.Option(help="Base URI for payload evaluation")] = "http://www.modsecurity.org/test", 53 | method: Annotated[str, typer.Option(help="Method")] = "", 54 | headers: Annotated[List[str], typer.Option('-H', '--header', help="List of headers")] = [], 55 | paranoia_level: Annotated[int, typer.Option('-PL', '--paranoia-level', help="Paranoia Level")] = 1, 56 | configs: Annotated[List[str], typer.Option('--config', help="List of additional configuration files (loaded BEFORE rules")] = ['conf/modsecurity.conf', 'conf/crs-setup.conf'], 57 | rules_path: Annotated[str, typer.Option('--rules', help="Rules location")] = 'coreruleset/rules', 58 | verbose: Annotated[bool, typer.Option('-v', '--verbose', help="Print matched rules with associated scores")] = False, 59 | version: Annotated[Optional[bool], typer.Option('-V', '--version', help="Print current ModSecurity version", callback=version)] = None, 60 | logs: Annotated[bool, typer.Option(help="Print libmodsecurity server logs")] = False): 61 | modsec = ModSecurity() 62 | 63 | if not logs: 64 | # disable ModSecurity callback logs 65 | modsec.setServerLogCb2(lambda x, y: None, LogProperty.RuleMessageLogProperty) 66 | 67 | if not method: 68 | method = 'POST' if request_body else 'GET' 69 | 70 | if not keys: 71 | keys = ['q'] 72 | 73 | encoded_query = urlencode(dict(zip(keys, payloads))) 74 | full_url = f"{base_uri}?{encoded_query}" 75 | parsed_url = urlparse(full_url) 76 | 77 | rules = RulesSet() 78 | 79 | # Load basic conf 80 | for config in configs: 81 | rules.loadFromUri(config) 82 | 83 | # Load rules 84 | for rule_path in sorted(glob.glob(f"{rules_path}/*.conf")): 85 | # Unsorted rules cause unexpcted behaviors for SETVAR 86 | rules.loadFromUri(rule_path) 87 | 88 | transaction = Transaction(modsec, rules) 89 | 90 | # URI 91 | if verbose: 92 | print(method, full_url) 93 | transaction.processURI(full_url, method, "2.0") 94 | 95 | # Headers 96 | headers.append(f"Host: {parsed_url.netloc}") # Avoid matching rule 920280 97 | for header in headers: 98 | name, value = header.split(':') 99 | transaction.addRequestHeader(name, value.strip()) # Avoid matching rule 920280 100 | transaction.processRequestHeaders() 101 | 102 | 103 | # Body 104 | if request_body: 105 | body = request_body.read().decode('utf-8') 106 | transaction.appendRequestBody(body) 107 | print(body) 108 | transaction.processRequestBody() 109 | 110 | # Decorate RuleMessages 111 | for rule in transaction.m_rulesMessages: 112 | rule.m_severity = Severity(rule.m_severity).score 113 | 114 | if verbose: 115 | print() 116 | print("# Matched rules") 117 | print() 118 | for rule in transaction.m_rulesMessages: 119 | print(' + ' if get_paranoia_level(rule) <= paranoia_level else ' - ', end='') 120 | print(f" {rule.m_ruleId} [+{rule.m_severity}/PL{get_paranoia_level(rule)}] - {rule.m_message}") 121 | 122 | if verbose: 123 | print("\nTotal Score (from matched rules): ", end="") 124 | 125 | total_score = sum([ rule.m_severity for rule in transaction.m_rulesMessages if get_paranoia_level(rule) <= paranoia_level]) 126 | print(total_score) 127 | 128 | 129 | if __name__ == "__main__": 130 | app() 131 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pymodsecurity==0.1.0 2 | typer 3 | --------------------------------------------------------------------------------