├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CREDITS.txt ├── Dockerfile ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.md ├── docs ├── Makefile ├── conf.py ├── index.rst └── wafw00f.8 ├── setup.py └── wafw00f ├── __init__.py ├── bin └── wafw00f ├── lib ├── __init__.py ├── asciiarts.py └── evillib.py ├── main.py ├── manager.py ├── plugins ├── __init__.py ├── aesecure.py ├── airee.py ├── airlock.py ├── alertlogic.py ├── aliyundun.py ├── anquanbao.py ├── anyu.py ├── applicationgateway.py ├── approach.py ├── armor.py ├── arvancloud.py ├── aspa.py ├── aspnetgen.py ├── astra.py ├── awswaf.py ├── azion.py ├── baidu.py ├── barikode.py ├── barracuda.py ├── bekchy.py ├── beluga.py ├── binarysec.py ├── bitninja.py ├── blockdos.py ├── bluedon.py ├── bulletproof.py ├── cachefly.py ├── cachewall.py ├── cdnns.py ├── cerber.py ├── chinacache.py ├── chuangyu.py ├── ciscoacexml.py ├── cloudbric.py ├── cloudflare.py ├── cloudfloordns.py ├── cloudfront.py ├── cloudprotector.py ├── comodo.py ├── crawlprotect.py ├── ddosguard.py ├── denyall.py ├── distil.py ├── dosarrest.py ├── dotdefender.py ├── dynamicweb.py ├── edgecast.py ├── eisoo.py ├── envoy.py ├── expressionengine.py ├── f5bigipapm.py ├── f5bigipasm.py ├── f5bigipltm.py ├── f5firepass.py ├── f5trafficshield.py ├── fastly.py ├── fortigate.py ├── fortiguard.py ├── fortiweb.py ├── frontdoor.py ├── gcparmor.py ├── godaddy.py ├── greywizard.py ├── huaweicloud.py ├── hyperguard.py ├── ibmdatapower.py ├── imunify360.py ├── incapsula.py ├── indusguard.py ├── instartdx.py ├── isaserver.py ├── janusec.py ├── jiasule.py ├── kemp.py ├── keycdn.py ├── knownsec.py ├── kona.py ├── limelight.py ├── litespeed.py ├── malcare.py ├── maxcdn.py ├── missioncontrol.py ├── modsecurity.py ├── naxsi.py ├── nemesida.py ├── netcontinuum.py ├── netscaler.py ├── nevisproxy.py ├── newdefend.py ├── nexusguard.py ├── ninja.py ├── nsfocus.py ├── nullddos.py ├── onmessage.py ├── openresty.py ├── oraclecloud.py ├── paloalto.py ├── pentawaf.py ├── perimeterx.py ├── pksec.py ├── powercdn.py ├── profense.py ├── ptaf.py ├── puhui.py ├── qcloud.py ├── qiniu.py ├── qrator.py ├── radware.py ├── reblaze.py ├── rsfirewall.py ├── rvmode.py ├── sabre.py ├── safe3.py ├── safedog.py ├── safeline.py ├── secking.py ├── secupress.py ├── secureentry.py ├── secureiis.py ├── securesphere.py ├── senginx.py ├── serverdefender.py ├── shadowd.py ├── shieldon.py ├── shieldsecurity.py ├── siteground.py ├── siteguard.py ├── sitelock.py ├── sonicwall.py ├── sophos.py ├── squarespace.py ├── squidproxy.py ├── stackpath.py ├── sucuri.py ├── tencent.py ├── teros.py ├── transip.py ├── uewaf.py ├── urlmaster.py ├── urlscan.py ├── variti.py ├── varnish.py ├── viettel.py ├── virusdie.py ├── wallarm.py ├── watchguard.py ├── webarx.py ├── webknight.py ├── webland.py ├── webray.py ├── webseal.py ├── webtotem.py ├── west263cdn.py ├── wordfence.py ├── wpmudev.py ├── wts.py ├── wzb360.py ├── xlabssecuritywaf.py ├── xuanwudun.py ├── yundun.py ├── yunsuo.py ├── yxlink.py ├── zenedge.py └── zscaler.py └── wafprio.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: sandrogauci, 0xInfection 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Command that reproduces the issue. e.g. `wafw00f http://example.org -a -vv` 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Desktop (please complete the following information):** 23 | - OS: [e.g. Windows, Linux] 24 | - OS version, distribution: 25 | - Python version: [e.g. python 3.2] 26 | 27 | **Debug output** 28 | Paste the output that you get when passing `-vv` to wafw00f. Example: 29 | 30 | ``` 31 | [*] Checking http://www.example.com 32 | INFO:wafw00f:starting wafw00f on http://www.example.com 33 | INFO:wafw00f:Request Succeeded 34 | INFO:wafw00f:Request Succeeded 35 | INFO:wafw00f:Checking for ACE XML Gateway (Cisco) 36 | INFO:wafw00f:Checking for aeSecure (aeSecure) 37 | INFO:wafw00f:Checking for AireeCDN (Airee) 38 | INFO:wafw00f:Checking for Airlock (Phion/Ergon) 39 | INFO:wafw00f:Checking for Alert Logic (Alert Logic) 40 | INFO:wafw00f:Checking for AliYunDun (Alibaba Cloud Computing) 41 | INFO:wafw00f:Checking for Anquanbao (Anquanbao) 42 | INFO:wafw00f:Checking for AnYu (AnYu Technologies) 43 | INFO:wafw00f:Checking for Approach (Approach) 44 | INFO:wafw00f:Checking for AppWall (Radware) 45 | INFO:wafw00f:Checking for Armor Defense (Armor) 46 | INFO:wafw00f:Checking for ArvanCloud (ArvanCloud) 47 | INFO:wafw00f:Checking for ASP.NET Generic (Microsoft) 48 | INFO:wafw00f:Checking for ASPA Firewall (ASPA Engineering Co.) 49 | INFO:wafw00f:Checking for Astra (Czar Securities) 50 | INFO:wafw00f:Checking for AzionCDN (AzionCDN) 51 | INFO:wafw00f:Checking for Barikode (Ethic Ninja) 52 | INFO:wafw00f:Checking for Barracuda (Barracuda Networks) 53 | INFO:wafw00f:Checking for Bekchy (Faydata Technologies Inc.) 54 | INFO:wafw00f:Checking for Beluga CDN (Beluga) 55 | INFO:wafw00f:Checking for BIG-IP Local Traffic Manager (F5 Networks) 56 | INFO:wafw00f:Checking for BinarySec (BinarySec) 57 | INFO:wafw00f:Checking for BitNinja (BitNinja) 58 | INFO:wafw00f:Checking for BlockDoS (BlockDoS) 59 | INFO:wafw00f:Checking for Bluedon (Bluedon IST) 60 | INFO:wafw00f:Checking for BulletProof Security Pro (AITpro Security) 61 | INFO:wafw00f:Checking for CacheWall (Varnish) 62 | INFO:wafw00f:Checking for CacheFly CDN (CacheFly) 63 | INFO:wafw00f:Checking for Comodo cWatch (Comodo CyberSecurity) 64 | INFO:wafw00f:Checking for CdnNS Application Gateway (CdnNs/WdidcNet) 65 | INFO:wafw00f:Checking for ChinaCache Load Balancer (ChinaCache) 66 | INFO:wafw00f:Checking for Chuang Yu Shield (Yunaq) 67 | INFO:wafw00f:Checking for Cloudbric (Penta Security) 68 | INFO:wafw00f:Checking for Cloudflare (Cloudflare Inc.) 69 | INFO:wafw00f:Checking for Cloudfloor (Cloudfloor DNS) 70 | INFO:wafw00f:Checking for Cloudfront (Amazon) 71 | INFO:wafw00f:Checking for CrawlProtect (Jean-Denis Brun) 72 | INFO:wafw00f:Checking for DataPower (IBM) 73 | INFO:wafw00f:Checking for DenyALL (Rohde & Schwarz CyberSecurity) 74 | INFO:wafw00f:Checking for Distil (Distil Networks) 75 | INFO:wafw00f:Checking for DOSarrest (DOSarrest Internet Security) 76 | INFO:wafw00f:Checking for DotDefender (Applicure Technologies) 77 | INFO:wafw00f:Checking for DynamicWeb Injection Check (DynamicWeb) 78 | INFO:wafw00f:Checking for Edgecast (Verizon Digital Media) 79 | INFO:wafw00f:Identified WAF: ['Edgecast (Verizon Digital Media)'] 80 | [+] The site http://www.example.com is behind Edgecast (Verizon Digital Media) WAF. 81 | [~] Number of requests: 2 82 | ``` 83 | 84 | **Additional context** 85 | Add any other context about the problem here. 86 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### Which category is this pull request? 2 | 3 | - [ ] A new feature/enhancement. 4 | - [ ] Fix an issue/feature-request. 5 | - [ ] An improvement to existing modules. 6 | - [ ] Other (Please mention below). 7 | 8 | #### Where has this been tested? 9 | 10 | - Python Version 11 | - [ ] v3.x 12 | - [ ] v2.x 13 | - Operating System: 14 | - [ ] Linux (Please specify distro) 15 | - [ ] Windows 16 | - [ ] MacOS 17 | 18 | #### Does this close any currently open issues? 19 | [Mention any issue which this PR closes] 20 | 21 | #### Does this add any new dependency? 22 | [Mention if this PR includes any new library] 23 | 24 | #### Does this add any new command line switch/argument? 25 | [Mention if the changes add any new arguments like `--arg`] 26 | 27 | #### Any other comments you would like to make? 28 | [Anything more you'd want the reviewer to know] 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *.swp 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | bin/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | eggs/ 17 | include/ 18 | local/ 19 | lib64/ 20 | man/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # Installer logs 29 | pip-log.txt 30 | pip-delete-this-directory.txt 31 | 32 | # Unit test / coverage reports 33 | htmlcov/ 34 | .tox/ 35 | .coverage 36 | .cache 37 | nosetests.xml 38 | coverage.xml 39 | 40 | # Translations 41 | *.mo 42 | 43 | # Mr Developer 44 | .mr.developer.cfg 45 | .project 46 | .pydevproject 47 | 48 | # Rope 49 | .ropeproject 50 | 51 | # Django stuff: 52 | *.log 53 | *.pot 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | *.csv 59 | *.json 60 | .idea/* 61 | .vscode/* 62 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at code@enablesecurity.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CREDITS.txt: -------------------------------------------------------------------------------- 1 | =================== 2 | THE WAFW00F PROJECT 3 | =================== 4 | 5 | $ AUTHORS 6 | ======= 7 | 8 | * Current Maintainers :- 9 | - Sandro Gauci 10 | - Pinaki Mondal <0xinfection [at] gmail [dot] com> 11 | 12 | * Original Code by :- 13 | - Sandro Gauci 14 | - Wendel G. Henrique 15 | 16 | $ CONTRIBUTORS 17 | ============ 18 | 19 | A number of people contributed in the past (in no particular order): 20 | 21 | - Sebastien Gioria 22 | - W3AF (or Andres Riancho) 23 | - Charlie Campbell 24 | - @j0eMcCray 25 | - Mathieu Dessus 26 | - David S. Langlands 27 | - Nmap's http-waf-fingerprint.nse / Hani Benhabiles 28 | - Denis Kolegov 29 | - kun a 30 | - Louis-Philippe Huberdeau 31 | - Brendan Coles 32 | - Matt Foster 33 | - g0tmi1k (?) 34 | - MyKings 35 | 36 | If you did contribute and somehow I didn't put your name in there, please do 37 | let me know at: . 38 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11.9-alpine 2 | WORKDIR /usr/src/app 3 | COPY . . 4 | RUN python setup.py install 5 | ENTRYPOINT [ "wafw00f" ] 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2024, WAFW00F Developers 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CREDITS.txt 2 | include LICENSE 3 | include MANIFEST.in 4 | include README.md 5 | include wafw00f/__init__.py 6 | include wafw00f/bin/wafw00f 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC_DIR = wafw00f 2 | DOC_DIR = docs 3 | MAKE = make 4 | 5 | all: 6 | make install 7 | make test 8 | make html 9 | make clean 10 | 11 | install: 12 | pip install -q -e .[dev,docs] 13 | 14 | lint: 15 | prospector $(SRC_DIR) --strictness veryhigh 16 | 17 | testall: 18 | tox 19 | 20 | html: 21 | cd $(DOC_DIR) && $(MAKE) html 22 | 23 | clean: 24 | rm -rf *.egg-info build dist .coverage 25 | find $(SRC_DIR) -name "*.pyc" | xargs rm -rf 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | wafw00f 3 |
4 | WAFW00F 5 |

6 |

7 | The Web Application Firewall Fingerprinting Tool. 8 |
9 | 10 | — From Enable Security 11 | 12 |

13 |

14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |

27 | 28 | ## How does it work? 29 | 30 | To do its magic, WAFW00F does the following: 31 | 32 | - Sends a _normal_ HTTP request and analyses the response; this identifies a 33 | number of WAF solutions. 34 | - If that is not successful, it sends a number of (potentially malicious) HTTP 35 | requests and uses simple logic to deduce which WAF it is. 36 | - If that is also not successful, it analyses the responses previously 37 | returned and uses another simple algorithm to guess if a WAF or security 38 | solution is actively responding to our attacks. 39 | 40 | For further details, check out the source code on our [main repository](https://github.com/EnableSecurity/wafw00f). 41 | 42 | ## What does it detect? 43 | 44 | WAFW00F can detect a number of firewalls, a list of which is as below: 45 | 46 | ``` 47 | $ wafw00f -l 48 | 49 | 50 | ? ,. ( . ) . " 51 | __ ?? (" ) )' ,' ) . (` '` 52 | (___()'`; ??? .; ) ' (( (" ) ;(, (( ( ;) " )") 53 | /,___ /` _"., ,._'_.,)_(..,( . )_ _' )_') (. _..( ' ) 54 | \\ \\ |____|____|____|____|____|____|____|____|____| 55 | 56 | ~ WAFW00F : v2.3.1 ~ 57 | ~ Sniffing Web Application Firewalls since 2014 ~ 58 | 59 | [+] Can test for these WAFs: 60 | 61 | WAF Name Manufacturer 62 | -------- ------------ 63 | 64 | 360WangZhanBao 360 Technologies 65 | ACE XML Gateway Cisco 66 | ASP.NET Generic Microsoft 67 | ASPA Firewall ASPA Engineering Co. 68 | AWS Elastic Load Balancer Amazon 69 | AireeCDN Airee 70 | Airlock Phion/Ergon 71 | Alert Logic Alert Logic 72 | AliYunDun Alibaba Cloud Computing 73 | AnYu AnYu Technologies 74 | Anquanbao Anquanbao 75 | AppWall Radware 76 | Approach Approach 77 | Armor Defense Armor 78 | ArvanCloud ArvanCloud 79 | Astra Czar Securities 80 | Azion Edge Firewall Azion 81 | Azure Application Gateway Microsoft 82 | Azure Front Door Microsoft 83 | BIG-IP AP Manager F5 Networks 84 | BIG-IP AppSec Manager F5 Networks 85 | BIG-IP Local Traffic Manager F5 Networks 86 | Barikode Ethic Ninja 87 | Barracuda Barracuda Networks 88 | Bekchy Faydata Technologies Inc. 89 | Beluga CDN Beluga 90 | BinarySec BinarySec 91 | BitNinja BitNinja 92 | BlockDoS BlockDoS 93 | Bluedon Bluedon IST 94 | BulletProof Security Pro AITpro Security 95 | CacheFly CDN CacheFly 96 | CacheWall Varnish 97 | CdnNS Application Gateway CdnNs/WdidcNet 98 | ChinaCache Load Balancer ChinaCache 99 | Chuang Yu Shield Yunaq 100 | Cloud Protector Rohde & Schwarz CyberSecurity 101 | Cloudbric Penta Security 102 | Cloudflare Cloudflare Inc. 103 | Cloudfloor Cloudfloor DNS 104 | Cloudfront Amazon 105 | Comodo cWatch Comodo CyberSecurity 106 | CrawlProtect Jean-Denis Brun 107 | DDoS-GUARD DDOS-GUARD CORP. 108 | DOSarrest DOSarrest Internet Security 109 | DataPower IBM 110 | DenyALL Rohde & Schwarz CyberSecurity 111 | Distil Distil Networks 112 | DotDefender Applicure Technologies 113 | DynamicWeb Injection Check DynamicWeb 114 | Edgecast Verizon Digital Media 115 | Eisoo Cloud Firewall Eisoo 116 | Envoy EnvoyProxy 117 | Expression Engine EllisLab 118 | Fastly Fastly CDN 119 | FirePass F5 Networks 120 | FortiGate Fortinet 121 | FortiGuard Fortinet 122 | FortiWeb Fortinet 123 | GoDaddy Website Protection GoDaddy 124 | Google Cloud App Armor Google Cloud 125 | Greywizard Grey Wizard 126 | Huawei Cloud Firewall Huawei 127 | HyperGuard Art of Defense 128 | ISA Server Microsoft 129 | Imunify360 CloudLinux 130 | Incapsula Imperva Inc. 131 | IndusGuard Indusface 132 | Instart DX Instart Logic 133 | Janusec Application Gateway Janusec 134 | Jiasule Jiasule 135 | KS-WAF KnownSec 136 | Kemp LoadMaster Progress Software 137 | KeyCDN KeyCDN 138 | Kona SiteDefender Akamai 139 | LimeLight CDN LimeLight 140 | LiteSpeed LiteSpeed Technologies 141 | Malcare Inactiv 142 | MaxCDN MaxCDN 143 | Mission Control Shield Mission Control 144 | ModSecurity SpiderLabs 145 | NAXSI NBS Systems 146 | NSFocus NSFocus Global Inc. 147 | Nemesida PentestIt 148 | NetContinuum Barracuda Networks 149 | NetScaler AppFirewall Citrix Systems 150 | NevisProxy AdNovum 151 | Newdefend NewDefend 152 | NexusGuard Firewall NexusGuard 153 | NinjaFirewall NinTechNet 154 | NullDDoS Protection NullDDoS 155 | OnMessage Shield BlackBaud 156 | Open-Resty Lua Nginx FLOSS 157 | Oracle Cloud Oracle 158 | PT Application Firewall Positive Technologies 159 | Palo Alto Next Gen Firewall Palo Alto Networks 160 | PentaWAF Global Network Services 161 | PerimeterX PerimeterX 162 | PowerCDN PowerCDN 163 | Profense ArmorLogic 164 | Puhui Puhui 165 | Qcloud Tencent Cloud 166 | Qiniu Qiniu CDN 167 | Qrator Qrator 168 | RSFirewall RSJoomla! 169 | RayWAF WebRay Solutions 170 | Reblaze Reblaze 171 | RequestValidationMode Microsoft 172 | SEnginx Neusoft 173 | Sabre Firewall Sabre 174 | Safe3 Web Firewall Safe3 175 | Safedog SafeDog 176 | Safeline Chaitin Tech. 177 | SecKing SecKing 178 | SecuPress WP Security SecuPress 179 | Secure Entry United Security Providers 180 | SecureSphere Imperva Inc. 181 | ServerDefender VP Port80 Software 182 | Shadow Daemon Zecure 183 | Shield Security One Dollar Plugin 184 | SiteGround SiteGround 185 | SiteGuard Sakura Inc. 186 | Sitelock TrueShield 187 | SonicWall Dell 188 | Squarespace Squarespace 189 | SquidProxy IDS SquidProxy 190 | StackPath StackPath 191 | Sucuri CloudProxy Sucuri Inc. 192 | Tencent Cloud Firewall Tencent Technologies 193 | Teros Citrix Systems 194 | Trafficshield F5 Networks 195 | TransIP Web Firewall TransIP 196 | UEWaf UCloud 197 | URLMaster SecurityCheck iFinity/DotNetNuke 198 | URLScan Microsoft 199 | UTM Web Protection Sophos 200 | Variti Variti 201 | Varnish OWASP 202 | Viettel Cloudrity 203 | VirusDie VirusDie LLC 204 | WP Cerber Security Cerber Tech 205 | WTS-WAF WTS 206 | Wallarm Wallarm Inc. 207 | WatchGuard WatchGuard Technologies 208 | WebARX WebARX Security Solutions 209 | WebKnight AQTRONIX 210 | WebLand WebLand 211 | WebSEAL IBM 212 | WebTotem WebTotem 213 | West263 CDN West263CDN 214 | Wordfence Defiant 215 | XLabs Security WAF XLabs 216 | Xuanwudun Xuanwudun 217 | YXLink YxLink Technologies 218 | Yundun Yundun 219 | Yunjiasu Baidu Cloud Computing 220 | Yunsuo Yunsuo 221 | ZScaler Accenture 222 | Zenedge Zenedge 223 | aeSecure aeSecure 224 | eEye SecureIIS BeyondTrust 225 | pkSecurity IDS pkSec 226 | wpmudev WAF Incsub 227 | Shieldon Firewall Shieldon.io 228 | ``` 229 | 230 | ## How do I use it? 231 | 232 | First, install the tools as described [here](#how-do-i-install-it). 233 | 234 | For help you can make use of the `--help` option. The basic usage is to pass 235 | an URL as an argument. Example: 236 | ``` 237 | $ wafw00f https://example.org 238 | 239 | ______ 240 | / \ 241 | ( Woof! ) 242 | \ ____/ ) 243 | ,, ) (_ 244 | .-. - _______ ( |__| 245 | ()``; |==|_______) .)|__| 246 | / (' /|\ ( |__| 247 | ( / ) / | \ . |__| 248 | \(_)_)) / | \ |__| 249 | 250 | ~ WAFW00F : v2.3.1 ~ 251 | The Web Application Firewall Fingerprinting Toolkit 252 | 253 | [*] Checking https://example.org 254 | [+] The site https://example.org is behind Edgecast (Verizon Digital Media) WAF. 255 | [~] Number of requests: 2 256 | ``` 257 | 258 | ## How do I install it? 259 | 260 | ### Install from PyPI (recommended) 261 | Run: 262 | ``` 263 | python3 -m pip install wafw00f 264 | ``` 265 | or 266 | ``` 267 | pip3 install wafw00f 268 | ``` 269 | 270 | ### Via Docker 271 | It is also possible to run it within a docker container. Clone this repository first and build the Docker image using: 272 | ``` 273 | docker build . -t wafw00f 274 | ``` 275 | Now you can run: 276 | ``` 277 | docker run --rm -it wafw00f https://example.com 278 | ``` 279 | 280 | ### From source 281 | > NOTE: Be careful to not break your system packages while installing wafw00f. Use venv as and when required. 282 | 283 | Clone the repository: 284 | ``` 285 | git clone https://github.com/enablesecurity/wafw00f.git 286 | ``` 287 | Then: 288 | ``` 289 | cd wafw00f/ 290 | python3 -m pip install . 291 | ``` 292 | 293 | Or, by using pipx directly: 294 | ``` 295 | pipx install git+https://github.com/EnableSecurity/wafw00f.git 296 | ``` 297 | 298 | ## Final Words 299 | 300 | __Questions?__ Pull up an [issue on GitHub Issue Tracker](https://github.com/enablesecurity/wafw00f/issues/new) or contact [me](mailto:sandro@enablesecurity.com). 301 | [Pull requests](https://github.com/enablesecurity/wafw00f/pulls), [ideas and issues](https://github.com/enablesecurity/wafw00f/issues) are highly welcome. 302 | 303 | Some useful links: 304 | 305 | - [Documentation/Wiki](https://github.com/enablesecurity/wafw00f/wiki/) 306 | - [Pypi Package Repository](https://pypi.org/project/wafw00f) 307 | 308 | Presently being developed and maintained by: 309 | 310 | - Sandro Gauci ([@SandroGauci](https://twitter.com/sandrogauci)) 311 | - Pinaki Mondal ([@0xInfection](https://twitter.com/0xinfection)) 312 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/wafw00f.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/wafw00f.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/wafw00f" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/wafw00f" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # wafw00f documentation build configuration file, created by 4 | # sphinx-quickstart on Thu May 15 20:04:22 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import os 16 | import sys 17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) 18 | sys.path.insert(0, BASE_DIR) 19 | from wafw00f import __version__ 20 | 21 | # If extensions (or modules to document with autodoc) are in another directory, 22 | # add these directories to sys.path here. If the directory is relative to the 23 | # documentation root, use os.path.abspath to make it absolute, like shown here. 24 | #sys.path.insert(0, os.path.abspath('.')) 25 | 26 | # -- General configuration ------------------------------------------------ 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | #needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # The suffix of source filenames. 40 | source_suffix = '.rst' 41 | 42 | # The encoding of source files. 43 | #source_encoding = 'utf-8-sig' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = u'wafw00f' 50 | copyright = u'2020, WAFW00F Developers' 51 | 52 | # The version info for the project you're documenting, acts as replacement for 53 | # |version| and |release|, also used in various other places throughout the 54 | # built documents. 55 | # 56 | # The short X.Y version. 57 | version = __version__ 58 | # The full version, including alpha/beta/rc tags. 59 | release = __version__ 60 | 61 | # The language for content autogenerated by Sphinx. Refer to documentation 62 | # for a list of supported languages. 63 | #language = None 64 | 65 | # There are two options for replacing |today|: either, you set today to some 66 | # non-false value, then it is used: 67 | #today = '' 68 | # Else, today_fmt is used as the format for a strftime call. 69 | #today_fmt = '%B %d, %Y' 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | exclude_patterns = ['_build'] 74 | 75 | # The reST default role (used for this markup: `text`) to use for all 76 | # documents. 77 | #default_role = None 78 | 79 | # If true, '()' will be appended to :func: etc. cross-reference text. 80 | #add_function_parentheses = True 81 | 82 | # If true, the current module name will be prepended to all description 83 | # unit titles (such as .. function::). 84 | #add_module_names = True 85 | 86 | # If true, sectionauthor and moduleauthor directives will be shown in the 87 | # output. They are ignored by default. 88 | #show_authors = False 89 | 90 | # The name of the Pygments (syntax highlighting) style to use. 91 | pygments_style = 'sphinx' 92 | 93 | # A list of ignored prefixes for module index sorting. 94 | #modindex_common_prefix = [] 95 | 96 | # If true, keep warnings as "system message" paragraphs in the built documents. 97 | #keep_warnings = False 98 | 99 | 100 | # -- Options for HTML output ---------------------------------------------- 101 | 102 | # The theme to use for HTML and HTML Help pages. See the documentation for 103 | # a list of builtin themes. 104 | html_theme = 'default' 105 | 106 | # Theme options are theme-specific and customize the look and feel of a theme 107 | # further. For a list of options available for each theme, see the 108 | # documentation. 109 | #html_theme_options = {} 110 | 111 | # Add any paths that contain custom themes here, relative to this directory. 112 | #html_theme_path = [] 113 | 114 | # The name for this set of Sphinx documents. If None, it defaults to 115 | # " v documentation". 116 | #html_title = None 117 | 118 | # A shorter title for the navigation bar. Default is the same as html_title. 119 | #html_short_title = None 120 | 121 | # The name of an image file (relative to this directory) to place at the top 122 | # of the sidebar. 123 | #html_logo = None 124 | 125 | # The name of an image file (within the static path) to use as favicon of the 126 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 127 | # pixels large. 128 | #html_favicon = None 129 | 130 | # Add any paths that contain custom static files (such as style sheets) here, 131 | # relative to this directory. They are copied after the builtin static files, 132 | # so a file named "default.css" will overwrite the builtin "default.css". 133 | html_static_path = ['_static'] 134 | 135 | # Add any extra paths that contain custom files (such as robots.txt or 136 | # .htaccess) here, relative to this directory. These files are copied 137 | # directly to the root of the documentation. 138 | #html_extra_path = [] 139 | 140 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 141 | # using the given strftime format. 142 | #html_last_updated_fmt = '%b %d, %Y' 143 | 144 | # If true, SmartyPants will be used to convert quotes and dashes to 145 | # typographically correct entities. 146 | #html_use_smartypants = True 147 | 148 | # Custom sidebar templates, maps document names to template names. 149 | #html_sidebars = {} 150 | 151 | # Additional templates that should be rendered to pages, maps page names to 152 | # template names. 153 | #html_additional_pages = {} 154 | 155 | # If false, no module index is generated. 156 | #html_domain_indices = True 157 | 158 | # If false, no index is generated. 159 | #html_use_index = True 160 | 161 | # If true, the index is split into individual pages for each letter. 162 | #html_split_index = False 163 | 164 | # If true, links to the reST sources are added to the pages. 165 | #html_show_sourcelink = True 166 | 167 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 168 | #html_show_sphinx = True 169 | 170 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 171 | #html_show_copyright = True 172 | 173 | # If true, an OpenSearch description file will be output, and all pages will 174 | # contain a tag referring to it. The value of this option must be the 175 | # base URL from which the finished HTML is served. 176 | #html_use_opensearch = '' 177 | 178 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 179 | #html_file_suffix = None 180 | 181 | # Output file base name for HTML help builder. 182 | htmlhelp_basename = 'wafw00fdoc' 183 | 184 | 185 | # -- Options for LaTeX output --------------------------------------------- 186 | 187 | latex_elements = { 188 | # The paper size ('letterpaper' or 'a4paper'). 189 | #'papersize': 'letterpaper', 190 | 191 | # The font size ('10pt', '11pt' or '12pt'). 192 | #'pointsize': '10pt', 193 | 194 | # Additional stuff for the LaTeX preamble. 195 | #'preamble': '', 196 | } 197 | 198 | # Grouping the document tree into LaTeX files. List of tuples 199 | # (source start file, target name, title, 200 | # author, documentclass [howto, manual, or own class]). 201 | latex_documents = [ 202 | ('index', 'wafw00f.tex', u'wafw00f Documentation', 203 | u'sandrogauci', 'manual'), 204 | ] 205 | 206 | # The name of an image file (relative to this directory) to place at the top of 207 | # the title page. 208 | #latex_logo = None 209 | 210 | # For "manual" documents, if this is true, then toplevel headings are parts, 211 | # not chapters. 212 | #latex_use_parts = False 213 | 214 | # If true, show page references after internal links. 215 | #latex_show_pagerefs = False 216 | 217 | # If true, show URL addresses after external links. 218 | #latex_show_urls = False 219 | 220 | # Documents to append as an appendix to all manuals. 221 | #latex_appendices = [] 222 | 223 | # If false, no module index is generated. 224 | #latex_domain_indices = True 225 | 226 | 227 | # -- Options for manual page output --------------------------------------- 228 | 229 | # One entry per manual page. List of tuples 230 | # (source start file, name, description, authors, manual section). 231 | man_pages = [ 232 | ('index', 'wafw00f', u'wafw00f Documentation', 233 | [u'sandrogauci'], 1) 234 | ] 235 | 236 | # If true, show URL addresses after external links. 237 | #man_show_urls = False 238 | 239 | 240 | # -- Options for Texinfo output ------------------------------------------- 241 | 242 | # Grouping the document tree into Texinfo files. List of tuples 243 | # (source start file, target name, title, author, 244 | # dir menu entry, description, category) 245 | texinfo_documents = [ 246 | ('index', 'wafw00f', u'wafw00f Documentation', 247 | u'sandrogauci', 'wafw00f', 'One line description of project.', 248 | 'Miscellaneous'), 249 | ] 250 | 251 | # Documents to append as an appendix to all manuals. 252 | #texinfo_appendices = [] 253 | 254 | # If false, no module index is generated. 255 | #texinfo_domain_indices = True 256 | 257 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 258 | #texinfo_show_urls = 'footnote' 259 | 260 | # If true, do not generate a @detailmenu in the "Top" node's menu. 261 | #texinfo_no_detailmenu = False 262 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. wafw00f documentation master file, created by 2 | sphinx-quickstart on Thu May 15 20:04:22 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to wafw00f's documentation! 7 | =================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | 15 | 16 | Indices and tables 17 | ================== 18 | 19 | * :ref:`genindex` 20 | * :ref:`modindex` 21 | * :ref:`search` 22 | 23 | -------------------------------------------------------------------------------- /docs/wafw00f.8: -------------------------------------------------------------------------------- 1 | .TH WAFW00F "8" "October 2020" "wafw00f " "User Commands" 2 | .SH NAME 3 | WAFW00F \- Identify and fingerprint Web Application Firewall products 4 | .SH SYNOPSIS 5 | .B wafw00f \fI\,url1 \/\fR[\fI\,url2 \/\fR[\fI\,url3 \/\fR... ]] 6 | .SH DESCRIPTION 7 | .TP 8 | The Web Application Firewall Identification and Fingerprinting Tool. 9 | .TP 10 | .TP 11 | To do its magic, WAFW00F does the following: 12 | Sends a normal HTTP request and analyses the response; this identifies a number of WAF solutions. 13 | If that is not successful, it sends a number of (potentially malicious) HTTP requests and uses simple logic to deduce which WAF it is. 14 | If that is also not successful, it analyses the responses previously returned and uses another simple algorithm to guess if a WAF or security solution is active> 15 | .SH OPTIONS 16 | .TP 17 | \fB\-h\fR, \fB\-\-help\fR 18 | Show available options. 19 | .TP 20 | \fB\-v\fR, \fB\-\-verbose\fR 21 | Enable verbosity \- multiple \fB\-v\fR options increase verbosity. 22 | .TP 23 | \fB\-a\fR, \fB\-\-findall\fR 24 | Find all WAFs, do not stop testing on the first one. 25 | .TP 26 | \fB\-r\fR, \fB\-\-noredirect\fR 27 | Do not follow redirections given by 3xx responses. 28 | .TP 29 | \fB\-t\fR WAF, \fB\-\-test\fR=\fI\,WAF\/\fR 30 | Test for one specific WAF product. 31 | .TP 32 | \fB\-o\fR OUTPUT, \fB\-\-output\fR=\fI\,OUTPUT\/\fR 33 | Write output to csv, json or text file depending on file extension. For stdout, specify - as filename. 34 | .TP 35 | \fB\-f\fR, \fB\-\-format\fR=\fI\,FORMAT\/\fR 36 | Force output format to csv, json or text. 37 | .TP 38 | \fB\-i\fR INPUT, \fB\-\-input\fR=\fI\,INPUT\/\fR 39 | Read targets from a file. Input format can be csv, json or text. For csv and json, a `url` column name or element is required. 40 | .TP 41 | \fB\-l\fR, \fB\-\-list\fR 42 | List all the WAFs that WAFW00F is able to detect. 43 | .TP 44 | \fB\-p\fR PROXY, \fB\-\-proxy\fR=\fI\,PROXY\/\fR 45 | Use an HTTP proxy to perform requests, example: http://hostname:8080, socks5://hostname:1080. 46 | .TP 47 | \fB\-V\fR, \fB\-\-version\fR 48 | Print out the version. 49 | .TP 50 | \fB\-H\fR FILE, \fB\-\-headers\fR=\fI\,FILE\/\fR 51 | Pass custom headers, for example to overwrite the default user\-agent string. 52 | .TP 53 | \fB\-T\fR TIMEOUT, \fB\-\-timeout\fR=\fI\,TIMEOUT\/\fR 54 | Set the timeout for the requests. 55 | .TP 56 | \fB\-\-no\-colors\fR 57 | Disable ANSI colors in output. 58 | .SH AUTHORS 59 | Sandro Gauci (@SandroGauci) 60 | .br 61 | Pinaki Mondal (@0xInfection) 62 | .SH REPORTING BUGS 63 | You can report bugs at the project homepage issue tracker: . 64 | .SH COPYRIGHT 65 | Copyright (C) 2009-2022 WAFW00F Developers. License: BSD 3-Clause . 66 | .br 67 | This is free software: you are free to modify and distribute under the terms as permitted by the license provided alongwith. 68 | .SH SEE ALSO 69 | Full documentation is available at: . 70 | .PP 71 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | import io 8 | from setuptools import setup, find_packages 9 | from os import path 10 | this_directory = path.abspath(path.dirname(__file__)) 11 | with io.open(path.join(this_directory, 'README.md'), encoding='utf-8') as f: 12 | desc = f.read() 13 | 14 | setup( 15 | name='wafw00f', 16 | version=__import__('wafw00f').__version__, 17 | long_description=desc, 18 | long_description_content_type='text/markdown', 19 | author='Sandro Gauci', 20 | author_email='sandro@enablesecurity.com', 21 | license='BSD License', 22 | url='https://github.com/enablesecurity/wafw00f', 23 | project_urls={ 24 | "Bug Tracker": "https://github.com/EnableSecurity/wafw00f/issues", 25 | "Documentation": "https://github.com/EnableSecurity/wafw00f/wiki", 26 | "Source Code": "https://github.com/EnableSecurity/wafw00f/tree/master" 27 | }, 28 | packages=find_packages(), 29 | install_requires=[ 30 | 'requests', 31 | 'requests[socks]', 32 | 'pluginbase' 33 | ], 34 | classifiers=[ 35 | 'Development Status :: 5 - Production/Stable', 36 | 'Intended Audience :: System Administrators', 37 | 'Intended Audience :: Information Technology', 38 | 'Topic :: Internet', 39 | 'Topic :: Security', 40 | 'Topic :: System :: Networking :: Firewalls', 41 | 'License :: OSI Approved :: BSD License', 42 | 'Programming Language :: Python :: 3', 43 | 'Operating System :: OS Independent' 44 | ], 45 | keywords='waf firewall detector fingerprint', 46 | extras_require={ 47 | 'dev': [ 48 | 'prospector' 49 | ], 50 | 'docs': [ 51 | 'Sphinx' 52 | ] 53 | }, 54 | entry_points={ 55 | 'console_scripts': [ 56 | 'wafw00f = wafw00f.main:main' 57 | ] 58 | } 59 | ) 60 | -------------------------------------------------------------------------------- /wafw00f/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | __version__ = '2.3.1' 4 | __license__ = 'BSD 3-Clause' 5 | -------------------------------------------------------------------------------- /wafw00f/bin/wafw00f: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | from wafw00f import main 5 | 6 | 7 | if __name__ == '__main__': 8 | main.main() 9 | -------------------------------------------------------------------------------- /wafw00f/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EnableSecurity/wafw00f/2b948682064825bc319b0b80d8992d9f661a3cbb/wafw00f/lib/__init__.py -------------------------------------------------------------------------------- /wafw00f/lib/asciiarts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | from dataclasses import dataclass 8 | from random import randint 9 | 10 | from wafw00f import __version__ 11 | 12 | 13 | @dataclass 14 | class Color: 15 | """ANSI colors.""" 16 | W: str = '\033[1;97m' 17 | Y: str = '\033[1;93m' 18 | G: str = '\033[1;92m' 19 | R: str = '\033[1;91m' 20 | B: str = '\033[1;94m' 21 | C: str = '\033[1;96m' 22 | E: str = '\033[0m' 23 | 24 | @classmethod 25 | def disable(cls): 26 | """Disables all colors.""" 27 | cls.W = '' 28 | cls.Y = '' 29 | cls.G = '' 30 | cls.R = '' 31 | cls.B = '' 32 | cls.C = '' 33 | cls.E = '' 34 | 35 | @classmethod 36 | def unpack(cls): 37 | """Unpacks and returns the color values. 38 | Useful for brevity, e.g.: 39 | (W,Y,G,R,B,C,E) = Color.unpack() 40 | """ 41 | return ( 42 | cls.W, 43 | cls.Y, 44 | cls.G, 45 | cls.R, 46 | cls.B, 47 | cls.C, 48 | cls.E 49 | ) 50 | 51 | 52 | def randomArt(): 53 | # Colors for terminal 54 | 55 | (W,Y,G,R,B,C,E) = Color.unpack() 56 | 57 | woof = ''' 58 | '''+W+'''______ 59 | '''+W+'''/ \\ 60 | '''+W+'''( Woof! ) 61 | '''+W+r'''\ ____/ '''+R+''') 62 | '''+W+''',, '''+R+''') ('''+Y+'''_ 63 | '''+Y+'''.-. '''+W+'''- '''+G+'''_______ '''+R+'''( '''+Y+'''|__| 64 | '''+Y+'''()``; '''+G+'''|==|_______) '''+R+'''.)'''+Y+'''|__| 65 | '''+Y+'''/ (' '''+G+r'''/|\ '''+R+'''( '''+Y+'''|__| 66 | '''+Y+'''( / ) '''+G+r''' / | \ '''+R+'''. '''+Y+'''|__| 67 | '''+Y+r'''\(_)_)) '''+G+r'''/ | \ '''+Y+'''|__|'''+E+''' 68 | 69 | '''+C+'~ WAFW00F : '+B+'v'+__version__+''' ~'''+W+''' 70 | The Web Application Firewall Fingerprinting Toolkit 71 | '''+E 72 | 73 | w00f = ''' 74 | '''+W+'''______ 75 | '''+W+'''/ \\ 76 | '''+W+'''( W00f! ) 77 | '''+W+r'''\ ____/ 78 | '''+W+''',, '''+G+'''__ '''+Y+'''404 Hack Not Found 79 | '''+C+'''|`-.__ '''+G+'''/ / '''+R+''' __ __ 80 | '''+C+'''/" _/ '''+G+'''/_/ '''+R+r'''\ \ / / 81 | '''+B+'''*===* '''+G+'''/ '''+R+r'''\ \_/ / '''+Y+'''405 Not Allowed 82 | '''+C+'''/ )__// '''+R+r'''\ / 83 | '''+C+'''/| / /---` '''+Y+'''403 Forbidden 84 | '''+C+r'''\\/` \ | '''+R+'''/ _ \\ 85 | '''+C+r'''`\ /_\\_ '''+Y+'''502 Bad Gateway '''+R+r'''/ / \ \ '''+Y+'''500 Internal Error 86 | '''+C+'''`_____``-` '''+R+r'''/_/ \_\\ 87 | 88 | '''+C+'~ WAFW00F : '+B+'v'+__version__+''' ~'''+W+''' 89 | The Web Application Firewall Fingerprinting Toolkit 90 | '''+E 91 | 92 | wo0f = r''' 93 | ? ,. ( . ) . " 94 | __ ?? (" ) )' ,' ) . (` '` 95 | (___()'`; ??? .; ) ' (( (" ) ;(, (( ( ;) " )") 96 | /,___ /` _"., ,._'_.,)_(..,( . )_ _' )_') (. _..( ' ) 97 | \\ \\ |____|____|____|____|____|____|____|____|____| 98 | 99 | ~ WAFW00F : v'''+__version__+''' ~ 100 | ~ Sniffing Web Application Firewalls since 2014 ~ 101 | ''' 102 | 103 | arts = [woof, w00f, wo0f] 104 | return arts[randint(0, len(arts)-1)] 105 | -------------------------------------------------------------------------------- /wafw00f/lib/evillib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | import time 8 | import logging 9 | from copy import copy 10 | 11 | import requests 12 | import urllib3 13 | 14 | # For requests < 2.16, this should be used. 15 | # requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 16 | # For requests >= 2.16, this is the convention 17 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 18 | 19 | def_headers = { 20 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 21 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:130.0) Gecko/20100101 Firefox/130.0', 22 | 'Accept-Language': 'en-US,en;q=0.5', 23 | 'Upgrade-Insecure-Requests': '1', 24 | 'Sec-Fetch-Dest': 'document', 25 | 'Sec-Fetch-Mode': 'navigate', 26 | 'Sec-Fetch-Site': 'cross-site', 27 | 'Priority': 'u=0, i', 28 | 'DNT': '1', 29 | } 30 | proxies = {} 31 | 32 | class waftoolsengine: 33 | def __init__( 34 | self, target='https://example.com', debuglevel=0, 35 | path='/', proxies=None, redir=True, head=None, timeout=7 36 | ): 37 | self.target = target 38 | self.debuglevel = debuglevel 39 | self.requestnumber = 0 40 | self.path = path 41 | self.redirectno = 0 42 | self.allowredir = redir 43 | self.proxies = proxies 44 | self.log = logging.getLogger('wafw00f') 45 | self.timeout = timeout 46 | if head: 47 | self.headers = head 48 | else: 49 | self.headers = copy(def_headers) #copy object by value not reference. Fix issue #90 50 | 51 | def Request(self, headers=None, path=None, params={}, delay=0): 52 | try: 53 | time.sleep(delay) 54 | if not headers: 55 | h = self.headers 56 | else: h = headers 57 | req = requests.get(self.target, proxies=self.proxies, headers=h, timeout=self.timeout, 58 | allow_redirects=self.allowredir, params=params, verify=False) 59 | self.log.info('Request Succeeded') 60 | self.log.debug('Headers: %s\n' % req.headers) 61 | self.log.debug('Content: %s\n' % req.content) 62 | self.requestnumber += 1 63 | return req 64 | except requests.exceptions.RequestException as e: 65 | self.log.error('Something went wrong %s' % (e.__str__())) 66 | -------------------------------------------------------------------------------- /wafw00f/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | ''' 4 | Copyright (C) 2024, WAFW00F Developers. 5 | See the LICENSE file for copying permission. 6 | ''' 7 | 8 | import csv 9 | import io 10 | import json 11 | import logging 12 | import os 13 | import random 14 | import re 15 | import sys 16 | import string 17 | import urllib.parse 18 | from collections import defaultdict 19 | from optparse import OptionParser 20 | 21 | from wafw00f import __license__, __version__ 22 | from wafw00f.lib.asciiarts import Color, randomArt 23 | from wafw00f.lib.evillib import waftoolsengine 24 | from wafw00f.manager import load_plugins 25 | from wafw00f.wafprio import wafdetectionsprio 26 | 27 | 28 | class WAFW00F(waftoolsengine): 29 | 30 | xsstring = r'' 31 | sqlistring = r'UNION SELECT ALL FROM information_schema AND " or SLEEP(5) or "' 32 | lfistring = r'../../etc/passwd' 33 | rcestring = r'/bin/cat /etc/passwd; ping 127.0.0.1; curl google.com' 34 | xxestring = r']>&hack;' 35 | 36 | def __init__(self, target='www.example.com', debuglevel=0, path='/', 37 | followredirect=True, extraheaders={}, proxies=None, timeout=7): 38 | 39 | self.log = logging.getLogger('wafw00f') 40 | self.attackres = None 41 | waftoolsengine.__init__(self, target, debuglevel, path, proxies, followredirect, extraheaders, timeout) 42 | self.knowledge = { 43 | 'generic': { 44 | 'found': False, 45 | 'reason': '' 46 | }, 47 | 'wafname': [] 48 | } 49 | self.rq = self.normalRequest() 50 | 51 | def normalRequest(self): 52 | return self.Request() 53 | 54 | def customRequest(self, headers=None): 55 | return self.Request( 56 | headers=headers 57 | ) 58 | 59 | def nonExistent(self): 60 | return self.Request( 61 | path=self.path + str(random.randrange(100, 999)) + '.html' 62 | ) 63 | 64 | def xssAttack(self): 65 | return self.Request( 66 | path=self.path, 67 | params={ 68 | create_random_param_name(): self.xsstring 69 | } 70 | ) 71 | 72 | def xxeAttack(self): 73 | return self.Request( 74 | path=self.path, 75 | params={ 76 | create_random_param_name(): self.xxestring 77 | } 78 | ) 79 | 80 | def lfiAttack(self): 81 | return self.Request( 82 | path=self.path + self.lfistring 83 | ) 84 | 85 | def centralAttack(self): 86 | return self.Request( 87 | path=self.path, 88 | params={ 89 | create_random_param_name(): self.xsstring, 90 | create_random_param_name(): self.sqlistring, 91 | create_random_param_name(): self.lfistring 92 | } 93 | ) 94 | 95 | def sqliAttack(self): 96 | return self.Request( 97 | path=self.path, 98 | params={ 99 | create_random_param_name(): self.sqlistring 100 | } 101 | ) 102 | 103 | def osciAttack(self): 104 | return self.Request( 105 | path=self.path, 106 | params= { 107 | create_random_param_name(): self.rcestring 108 | } 109 | ) 110 | 111 | def performCheck(self, request_method): 112 | r = request_method() 113 | if r is None: 114 | raise RequestBlocked() 115 | return r, r.url 116 | 117 | # Most common attacks used to detect WAFs 118 | attcom = [xssAttack, sqliAttack, lfiAttack] 119 | attacks = [xssAttack, xxeAttack, lfiAttack, sqliAttack, osciAttack] 120 | 121 | def genericdetect(self): 122 | reason = '' 123 | reasons = ['Blocking is being done at connection/packet level.', 124 | 'The server header is different when an attack is detected.', 125 | 'The server returns a different response code when an attack string is used.', 126 | 'It closed the connection for a normal request.', 127 | 'The response was different when the request wasn\'t made from a browser.' 128 | ] 129 | try: 130 | # Testing for no user-agent response. Detects almost all WAFs out there. 131 | resp1, _ = self.performCheck(self.normalRequest) 132 | if 'User-Agent' in self.headers: 133 | self.headers.pop('User-Agent') # Deleting the user-agent key from object not dict. 134 | resp3 = self.customRequest(headers=self.headers) 135 | if resp3 is not None and resp1 is not None: 136 | if resp1.status_code != resp3.status_code: 137 | self.log.info('Server returned a different response when request didn\'t contain the User-Agent header.') 138 | reason = reasons[4] 139 | reason += '\r\n' 140 | reason += 'Normal response code is "%s",' % resp1.status_code 141 | reason += ' while the response code to a modified request is "%s"' % resp3.status_code 142 | self.knowledge['generic']['reason'] = reason 143 | self.knowledge['generic']['found'] = True 144 | return True 145 | 146 | # Testing the status code upon sending a xss attack 147 | resp2, xss_url = self.performCheck(self.xssAttack) 148 | if resp1.status_code != resp2.status_code: 149 | self.log.info('Server returned a different response when a XSS attack vector was tried.') 150 | reason = reasons[2] 151 | reason += '\r\n' 152 | reason += 'Normal response code is "%s",' % resp1.status_code 153 | reason += ' while the response code to cross-site scripting attack is "%s"' % resp2.status_code 154 | self.knowledge['generic']['reason'] = reason 155 | self.knowledge['generic']['found'] = True 156 | return xss_url 157 | 158 | # Testing the status code upon sending a lfi attack 159 | resp2, lfi_url = self.performCheck(self.lfiAttack) 160 | if resp1.status_code != resp2.status_code: 161 | self.log.info('Server returned a different response when a directory traversal was attempted.') 162 | reason = reasons[2] 163 | reason += '\r\n' 164 | reason += 'Normal response code is "%s",' % resp1.status_code 165 | reason += ' while the response code to a file inclusion attack is "%s"' % resp2.status_code 166 | self.knowledge['generic']['reason'] = reason 167 | self.knowledge['generic']['found'] = True 168 | return lfi_url 169 | 170 | # Testing the status code upon sending a sqli attack 171 | resp2, sqli_url = self.performCheck(self.sqliAttack) 172 | if resp1.status_code != resp2.status_code: 173 | self.log.info('Server returned a different response when a SQLi was attempted.') 174 | reason = reasons[2] 175 | reason += '\r\n' 176 | reason += 'Normal response code is "%s",' % resp1.status_code 177 | reason += ' while the response code to a SQL injection attack is "%s"' % resp2.status_code 178 | self.knowledge['generic']['reason'] = reason 179 | self.knowledge['generic']['found'] = True 180 | return sqli_url 181 | 182 | # Checking for the Server header after sending malicious requests 183 | normalserver, attackresponse_server = '', '' 184 | response = self.attackres 185 | if 'server' in resp1.headers: 186 | normalserver = resp1.headers.get('Server') 187 | if response is not None and 'server' in response.headers: 188 | attackresponse_server = response.headers.get('Server') 189 | if attackresponse_server != normalserver: 190 | self.log.info('Server header changed, WAF possibly detected') 191 | self.log.debug('Attack response: %s' % attackresponse_server) 192 | self.log.debug('Normal response: %s' % normalserver) 193 | reason = reasons[1] 194 | reason += '\r\nThe server header for a normal response is "%s",' % normalserver 195 | reason += ' while the server header a response to an attack is "%s",' % attackresponse_server 196 | self.knowledge['generic']['reason'] = reason 197 | self.knowledge['generic']['found'] = True 198 | return True 199 | 200 | # If at all request doesn't go, press F 201 | except RequestBlocked: 202 | self.knowledge['generic']['reason'] = reasons[0] 203 | self.knowledge['generic']['found'] = True 204 | return True 205 | return False 206 | 207 | def matchHeader(self, headermatch, attack=False): 208 | if attack: 209 | r = self.attackres 210 | else: 211 | r = self.rq 212 | if r is None: 213 | return 214 | 215 | header, match = headermatch 216 | headerval = r.headers.get(header) 217 | if headerval: 218 | # set-cookie can have multiple headers, python gives it to us 219 | # concatinated with a comma 220 | if header == 'Set-Cookie': 221 | headervals = headerval.split(', ') 222 | else: 223 | headervals = [headerval] 224 | for headerval in headervals: 225 | if re.search(match, headerval, re.I): 226 | return True 227 | return False 228 | 229 | def matchStatus(self, statuscode, attack=True): 230 | if attack: 231 | r = self.attackres 232 | else: 233 | r = self.rq 234 | if r is None: 235 | return 236 | if r.status_code == statuscode: 237 | return True 238 | return False 239 | 240 | def matchCookie(self, match, attack=False): 241 | return self.matchHeader(('Set-Cookie', match), attack=attack) 242 | 243 | def matchReason(self, reasoncode, attack=True): 244 | if attack: 245 | r = self.attackres 246 | else: 247 | r = self.rq 248 | if r is None: 249 | return 250 | # We may need to match multiline context in response body 251 | if str(r.reason) == reasoncode: 252 | return True 253 | return False 254 | 255 | def matchContent(self, regex, attack=True): 256 | if attack: 257 | r = self.attackres 258 | else: 259 | r = self.rq 260 | if r is None: 261 | return 262 | # We may need to match multiline context in response body 263 | if re.search(regex, r.text, re.I): 264 | return True 265 | return False 266 | 267 | wafdetections = dict() 268 | 269 | plugin_dict = load_plugins() 270 | result_dict = {} 271 | for plugin_module in plugin_dict.values(): 272 | wafdetections[plugin_module.NAME] = plugin_module.is_waf 273 | # Check for prioritized ones first, then check those added externally 274 | checklist = wafdetectionsprio 275 | checklist += list(set(wafdetections.keys()) - set(checklist)) 276 | 277 | def identwaf(self, findall=False): 278 | detected = list() 279 | try: 280 | self.attackres, xurl = self.performCheck(self.centralAttack) 281 | except RequestBlocked: 282 | return detected, None 283 | for wafvendor in self.checklist: 284 | self.log.info('Checking for %s' % wafvendor) 285 | if self.wafdetections[wafvendor](self): 286 | detected.append(wafvendor) 287 | if not findall: 288 | break 289 | self.knowledge['wafname'] = detected 290 | return detected, xurl 291 | 292 | def calclogginglevel(verbosity): 293 | default = 40 # errors are printed out 294 | level = default - (verbosity * 10) 295 | if level < 0: 296 | level = 0 297 | return level 298 | 299 | def buildResultRecord(url, waf, evil_url=None): 300 | result = {} 301 | result['url'] = url 302 | if waf: 303 | result['detected'] = True 304 | if waf == 'generic': 305 | result['trigger_url'] = evil_url 306 | result['firewall'] = 'Generic' 307 | result['manufacturer'] = 'Unknown' 308 | else: 309 | result['trigger_url'] = evil_url 310 | result['firewall'] = waf.split('(')[0].strip() 311 | result['manufacturer'] = waf.split('(')[1].replace(')', '').strip() 312 | else: 313 | result['trigger_url'] = evil_url 314 | result['detected'] = False 315 | result['firewall'] = 'None' 316 | result['manufacturer'] = 'None' 317 | return result 318 | 319 | def getTextResults(res=[]): 320 | # leaving out some space for future possibilities of newer columns 321 | # newer columns can be added to this tuple below 322 | keys = ('detected') 323 | res = [({key: ba[key] for key in ba if key not in keys}) for ba in res] 324 | rows = [] 325 | for dk in res: 326 | p = [str(x) for _, x in dk.items()] 327 | rows.append(p) 328 | for m in rows: 329 | m[1] = '%s (%s)' % (m[1], m[2]) 330 | m.pop() 331 | defgen = [ 332 | (max([len(str(row[i])) for row in rows]) + 3) 333 | for i in range(len(rows[0])) 334 | ] 335 | rwfmt = ''.join(['{:>'+str(dank)+'}' for dank in defgen]) 336 | textresults = [] 337 | for row in rows: 338 | textresults.append(rwfmt.format(*row)) 339 | return textresults 340 | 341 | def create_random_param_name(size=8, chars=string.ascii_lowercase): 342 | return ''.join(random.choice(chars) for _ in range(size)) 343 | 344 | def disableStdOut(): 345 | sys.stdout = None 346 | 347 | def enableStdOut(): 348 | sys.stdout = sys.__stdout__ 349 | 350 | def getheaders(fn): 351 | headers = {} 352 | if not os.path.exists(fn): 353 | logging.getLogger('wafw00f').critical('Headers file "%s" does not exist!' % fn) 354 | return 355 | with io.open(fn, 'r', encoding='utf-8') as f: 356 | for line in f.readlines(): 357 | _t = line.split(':', 2) 358 | if len(_t) == 2: 359 | h, v = map(lambda x: x.strip(), _t) 360 | headers[h] = v 361 | return headers 362 | 363 | class RequestBlocked(Exception): 364 | pass 365 | 366 | def main(): 367 | parser = OptionParser(usage='%prog url1 [url2 [url3 ... ]]\r\nexample: %prog http://www.victim.org/') 368 | parser.add_option('-v', '--verbose', action='count', dest='verbose', default=0, 369 | help='Enable verbosity, multiple -v options increase verbosity') 370 | parser.add_option('-a', '--findall', action='store_true', dest='findall', default=False, 371 | help='Find all WAFs which match the signatures, do not stop testing on the first one') 372 | parser.add_option('-r', '--noredirect', action='store_false', dest='followredirect', 373 | default=True, help='Do not follow redirections given by 3xx responses') 374 | parser.add_option('-t', '--test', dest='test', help='Test for one specific WAF') 375 | parser.add_option('-o', '--output', dest='output', help='Write output to csv, json or text file depending on file extension. For stdout, specify - as filename.', 376 | default=None) 377 | parser.add_option('-f', '--format', dest='format', help='Force output format to csv, json or text.', 378 | default=None) 379 | parser.add_option('-i', '--input-file', dest='input', help='Read targets from a file. Input format can be csv, json or text. For csv and json, a `url` column name or element is required.', 380 | default=None) 381 | parser.add_option('-l', '--list', dest='list', action='store_true', 382 | default=False, help='List all WAFs that WAFW00F is able to detect') 383 | parser.add_option('-p', '--proxy', dest='proxy', default=None, 384 | help='Use an HTTP proxy to perform requests, examples: http://hostname:8080, socks5://hostname:1080, http://user:pass@hostname:8080') 385 | parser.add_option('--version', '-V', dest='version', action='store_true', 386 | default=False, help='Print out the current version of WafW00f and exit.') 387 | parser.add_option('--headers', '-H', dest='headers', action='store', default=None, 388 | help='Pass custom headers via a text file to overwrite the default header set.') 389 | parser.add_option('-T', '--timeout', dest='timeout', action='store', default=7, type=int, 390 | help='Set the timeout for the requests.') 391 | parser.add_option('--no-colors', dest='colors', action='store_false', 392 | default=True, help='Disable ANSI colors in output.') 393 | 394 | options, args = parser.parse_args() 395 | 396 | logging.basicConfig(level=calclogginglevel(options.verbose)) 397 | log = logging.getLogger('wafw00f') 398 | if options.output == '-': 399 | disableStdOut() 400 | 401 | # Windows based systems do not support ANSI sequences, 402 | # hence not displaying them. 403 | if not options.colors or 'win' in sys.platform: 404 | Color.disable() 405 | 406 | print(randomArt()) 407 | (W,Y,G,R,B,C,E) = Color.unpack() 408 | 409 | if options.list: 410 | print('[+] Can test for these WAFs:\r\n') 411 | try: 412 | m = [i.replace(')', '').split(' (') for i in wafdetectionsprio] 413 | print(R+' WAF Name'+' '*24+'Manufacturer\n '+'-'*8+' '*24+'-'*12+'\n') 414 | max_len = max(len(str(x)) for k in m for x in k) 415 | for inner in m: 416 | first = True 417 | for elem in inner: 418 | if first: 419 | text = Y+' {:<{}} '.format(elem, max_len+2) 420 | first = False 421 | else: 422 | text = W+'{:<{}} '.format(elem, max_len+2) 423 | print(text, E, end='') 424 | print() 425 | sys.exit(0) 426 | except Exception: 427 | return 428 | if options.version: 429 | print('[+] The version of WAFW00F you have is %sv%s%s' % (B, __version__, E)) 430 | print('[+] WAFW00F is provided under the %s%s%s license.' % (C, __license__, E)) 431 | return 432 | extraheaders = {} 433 | if options.headers: 434 | log.info('Getting extra headers from %s' % options.headers) 435 | extraheaders = getheaders(options.headers) 436 | if extraheaders is None: 437 | parser.error('Please provide a headers file with colon delimited header names and values') 438 | if len(args) == 0 and not options.input: 439 | parser.error('No test target specified.') 440 | #check if input file is present 441 | if options.input: 442 | log.debug('Loading file "%s"' % options.input) 443 | try: 444 | if options.input.endswith('.json'): 445 | with open(options.input) as f: 446 | try: 447 | urls = json.loads(f.read()) 448 | except json.decoder.JSONDecodeError: 449 | log.critical('JSON file %s did not contain well-formed JSON', options.input) 450 | sys.exit(1) 451 | log.info('Found: %s urls to check.' %(len(urls))) 452 | targets = [ item['url'] for item in urls ] 453 | elif options.input.endswith('.csv'): 454 | columns = defaultdict(list) 455 | with open(options.input) as f: 456 | reader = csv.DictReader(f) 457 | for row in reader: 458 | for (k,v) in row.items(): 459 | columns[k].append(v) 460 | targets = columns['url'] 461 | else: 462 | with open(options.input) as f: 463 | targets = [x for x in f.read().splitlines()] 464 | except FileNotFoundError: 465 | log.error('File %s could not be read. No targets loaded.', options.input) 466 | sys.exit(1) 467 | else: 468 | targets = args 469 | results = [] 470 | for target in targets: 471 | if not target.startswith('http'): 472 | log.info('The url %s should start with http:// or https:// .. fixing (might make this unusable)' % target) 473 | target = 'https://' + target 474 | print('[*] Checking %s' % target) 475 | pret = urllib.parse.urlparse(target) 476 | if pret is None: 477 | log.critical('The url %s is not well formed' % target) 478 | sys.exit(1) 479 | log.info('starting wafw00f on %s' % target) 480 | proxies = dict() 481 | if options.proxy: 482 | proxies = { 483 | 'http': options.proxy, 484 | 'https': options.proxy, 485 | } 486 | attacker = WAFW00F(target, debuglevel=options.verbose, path=pret.path, 487 | followredirect=options.followredirect, extraheaders=extraheaders, 488 | proxies=proxies, timeout=options.timeout) 489 | if attacker.rq is None: 490 | log.error('Site %s appears to be down' % pret.hostname) 491 | continue 492 | if options.test: 493 | if options.test in attacker.wafdetections: 494 | waf = attacker.wafdetections[options.test](attacker) 495 | if waf: 496 | print('[+] The site %s%s%s is behind %s%s%s WAF.' % (B, target, E, C, options.test, E)) 497 | else: 498 | print('[-] WAF %s was not detected on %s' % (options.test, target)) 499 | else: 500 | print('[-] WAF %s was not found in our list\r\nUse the --list option to see what is available' % options.test) 501 | return 502 | waf, xurl = attacker.identwaf(options.findall) 503 | log.info('Identified WAF: %s' % waf) 504 | if len(waf) > 0: 505 | for i in waf: 506 | results.append(buildResultRecord(target, i, xurl)) 507 | print('[+] The site %s%s%s is behind %s%s%s WAF.' % (B, target, E, C, (E+' and/or '+C).join(waf), E)) 508 | if (options.findall) or len(waf) == 0: 509 | print('[+] Generic Detection results:') 510 | generic_url = attacker.genericdetect() 511 | if generic_url: 512 | log.info('Generic Detection: %s' % attacker.knowledge['generic']['reason']) 513 | print('[*] The site %s seems to be behind a WAF or some sort of security solution' % target) 514 | print('[~] Reason: %s' % attacker.knowledge['generic']['reason']) 515 | results.append(buildResultRecord(target, 'generic', generic_url)) 516 | else: 517 | print('[-] No WAF detected by the generic detection') 518 | results.append(buildResultRecord(target, None, None)) 519 | print('[~] Number of requests: %s' % attacker.requestnumber) 520 | #print table of results 521 | if len(results) > 0: 522 | log.info('Found: %s matches.' % (len(results))) 523 | if options.output: 524 | if options.output == '-': 525 | enableStdOut() 526 | if options.format == 'json': 527 | json.dump(results, sys.stdout, indent=2, sort_keys=True) 528 | elif options.format == 'csv': 529 | csvwriter = csv.writer(sys.stdout, delimiter=',', quotechar='"', 530 | quoting=csv.QUOTE_MINIMAL) 531 | count = 0 532 | for result in results: 533 | if count == 0: 534 | header = result.keys() 535 | csvwriter.writerow(header) 536 | count += 1 537 | csvwriter.writerow(result.values()) 538 | else: 539 | print(os.linesep.join(getTextResults(results))) 540 | elif options.output.endswith('.json'): 541 | log.debug('Exporting data in json format to file: %s' % (options.output)) 542 | with open(options.output, 'w') as outfile: 543 | json.dump(results, outfile, indent=2, sort_keys=True) 544 | elif options.output.endswith('.csv'): 545 | log.debug('Exporting data in csv format to file: %s' % (options.output)) 546 | with open(options.output, 'w') as outfile: 547 | csvwriter = csv.writer(outfile, delimiter=',', quotechar='"', 548 | quoting=csv.QUOTE_MINIMAL) 549 | count = 0 550 | for result in results: 551 | if count == 0: 552 | header = result.keys() 553 | csvwriter.writerow(header) 554 | count += 1 555 | csvwriter.writerow(result.values()) 556 | else: 557 | log.debug('Exporting data in text format to file: %s' % (options.output)) 558 | if options.format == 'json': 559 | with open(options.output, 'w') as outfile: 560 | json.dump(results, outfile, indent=2, sort_keys=True) 561 | elif options.format == 'csv': 562 | with open(options.output, 'w') as outfile: 563 | csvwriter = csv.writer(outfile, delimiter=',', quotechar='"', 564 | quoting=csv.QUOTE_MINIMAL) 565 | count = 0 566 | for result in results: 567 | if count == 0: 568 | header = result.keys() 569 | csvwriter.writerow(header) 570 | count += 1 571 | csvwriter.writerow(result.values()) 572 | else: 573 | with open(options.output, 'w') as outfile: 574 | outfile.write(os.linesep.join(getTextResults(results))) 575 | 576 | if __name__ == '__main__': 577 | version_info = sys.version_info 578 | if version_info.major < 3 or (version_info.major == 3 and version_info.minor < 6): 579 | sys.stderr.write('Your version of python is way too old... please update to 3.6 or later\r\n') 580 | main() 581 | -------------------------------------------------------------------------------- /wafw00f/manager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | import os 8 | from functools import partial 9 | from pluginbase import PluginBase 10 | 11 | def load_plugins(): 12 | here = os.path.abspath(os.path.dirname(__file__)) 13 | get_path = partial(os.path.join, here) 14 | plugin_dir = get_path('plugins') 15 | 16 | plugin_base = PluginBase( 17 | package='wafw00f.plugins', searchpath=[plugin_dir] 18 | ) 19 | plugin_source = plugin_base.make_plugin_source( 20 | searchpath=[plugin_dir], persist=True 21 | ) 22 | 23 | plugin_dict = {} 24 | for plugin_name in plugin_source.list_plugins(): 25 | plugin_dict[plugin_name] = plugin_source.load_plugin(plugin_name) 26 | 27 | return plugin_dict 28 | -------------------------------------------------------------------------------- /wafw00f/plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EnableSecurity/wafw00f/2b948682064825bc319b0b80d8992d9f661a3cbb/wafw00f/plugins/__init__.py -------------------------------------------------------------------------------- /wafw00f/plugins/aesecure.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'aeSecure (aeSecure)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('aeSecure-code', '.+?')): 12 | return True 13 | 14 | if self.matchContent(r'aesecure_denied\.png'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/airee.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'AireeCDN (Airee)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'Airee')): 12 | return True 13 | 14 | if self.matchHeader(('X-Cache', r'(\w+\.)?airee\.cloud')): 15 | return True 16 | 17 | if self.matchContent(r'airee\.cloud'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/airlock.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Airlock (Phion/Ergon)' 8 | 9 | 10 | def is_waf(self): 11 | # This method of detection is old (though most reliable), so we check it first 12 | if self.matchCookie(r'^al[_-]?(sess|lb)='): 13 | return True 14 | 15 | if self.matchContent(r'server detected a syntax error in your request'): 16 | return True 17 | 18 | return False 19 | -------------------------------------------------------------------------------- /wafw00f/plugins/alertlogic.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Alert Logic (Alert Logic)' 8 | 9 | 10 | def is_waf(self): 11 | if not self.matchContent(r'<(title|h\d{1})>requested url cannot be found'): 12 | return False 13 | 14 | if not self.matchContent(r'we are sorry.{0,10}?but the page you are looking for cannot be found'): 15 | return False 16 | 17 | if not self.matchContent(r'back to previous page'): 18 | return False 19 | 20 | if not self.matchContent(r'proceed to homepage'): 21 | return False 22 | 23 | if not self.matchContent(r'reference id'): 24 | return False 25 | 26 | return True 27 | -------------------------------------------------------------------------------- /wafw00f/plugins/aliyundun.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'AliYunDun (Alibaba Cloud Computing)' 8 | 9 | 10 | def is_waf(self): 11 | if not self.matchContent(r'error(s)?\.aliyun(dun)?\.(com|net)?'): 12 | return False 13 | 14 | if not self.matchContent(r'alicdn\.com\/sd\-base\/static\/\d{1,2}\.\d{1,2}\.\d{1,2}\/image\/405\.png'): 15 | return False 16 | 17 | if not self.matchContent(r'Sorry, your request has been blocked as it may cause potential threats to the server\'s security.'): 18 | return False 19 | 20 | if not self.matchStatus(405): 21 | return False 22 | 23 | return True 24 | -------------------------------------------------------------------------------- /wafw00f/plugins/anquanbao.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Anquanbao (Anquanbao)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-Powered-By-Anquanbao', '.+?')): 12 | return True 13 | 14 | if self.matchContent(r'aqb_cc/error/'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/anyu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'AnYu (AnYu Technologies)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'anyu.{0,10}?the green channel'): 12 | return True 13 | 14 | if self.matchContent(r'your access has been intercepted by anyu'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/applicationgateway.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Azure Application Gateway (Microsoft)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'
Microsoft-Azure-Application-Gateway/v2
') and \ 12 | self.matchContent(r'

403 Forbidden

'): 13 | return True 14 | 15 | return False 16 | -------------------------------------------------------------------------------- /wafw00f/plugins/approach.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Approach (Approach)' 8 | 9 | 10 | def is_waf(self): 11 | # This method of detection is old (though most reliable), so we check it first 12 | if self.matchContent(r'approach.{0,10}?web application (firewall|filtering)'): 13 | return True 14 | 15 | if self.matchContent(r'approach.{0,10}?infrastructure team'): 16 | return True 17 | 18 | return False 19 | -------------------------------------------------------------------------------- /wafw00f/plugins/armor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Armor Defense (Armor)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'blocked by website protection from armor'): 12 | return True 13 | 14 | if self.matchContent(r'please create an armor support ticket'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/arvancloud.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'ArvanCloud (ArvanCloud)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'ArvanCloud')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/aspa.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'ASPA Firewall (ASPA Engineering Co.)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'ASPA[\-_]?WAF')): 12 | return True 13 | 14 | if self.matchHeader(('ASPA-Cache-Status', r'.+?')): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/aspnetgen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'ASP.NET Generic (Microsoft)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'iis (\d+.)+?detailed error'): 12 | return True 13 | 14 | if self.matchContent(r'potentially dangerous request querystring'): 15 | return True 16 | 17 | if self.matchContent(r'application error from being viewed remotely (for security reasons)?'): 18 | return True 19 | 20 | if self.matchContent(r'An application error occurred on the server'): 21 | return True 22 | 23 | return False 24 | -------------------------------------------------------------------------------- /wafw00f/plugins/astra.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Astra (Czar Securities)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie(r'^cz_astra_csrf_cookie'): 12 | return True 13 | 14 | if self.matchContent(r'astrawebsecurity\.freshdesk\.com'): 15 | return True 16 | 17 | if self.matchContent(r'www\.getastra\.com/assets/images'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/awswaf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'AWS Elastic Load Balancer (Amazon)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-AMZ-ID', '.+?')): 12 | return True 13 | 14 | if self.matchHeader(('X-AMZ-Request-ID', '.+?')): 15 | return True 16 | 17 | if self.matchCookie(r'^aws.?alb='): 18 | return True 19 | 20 | if self.matchHeader(('Server', r'aws.?elb'), attack=True): 21 | return True 22 | 23 | if self.matchHeader(('X-Blocked-By-WAF', 'Blocked_by_custom_response_for_AWSManagedRules.*')): 24 | return True 25 | 26 | return False 27 | -------------------------------------------------------------------------------- /wafw00f/plugins/azion.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Azion Edge Firewall (Azion)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('x-azion-edge-pop', r'.+?')): 12 | return True 13 | 14 | if self.matchHeader(('x-azion-request-id', r'.+?')): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/baidu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Yunjiasu (Baidu Cloud Computing)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'yunjiasu.*')): 12 | return True 13 | 14 | if self.matchContent(r'href="/.well-known/yunjiasu-cgi/'): 15 | return True 16 | 17 | if self.matchContent(r"document.cookie='yjs_use_ob=0"): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/barikode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Barikode (Ethic Ninja)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'barikode<.strong>'): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/barracuda.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Barracuda (Barracuda Networks)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie(r'^barra_counter_session='): 12 | return True 13 | 14 | if self.matchCookie(r'^BNI__BARRACUDA_LB_COOKIE='): 15 | return True 16 | 17 | if self.matchCookie(r'^BNI_persistence='): 18 | return True 19 | 20 | if self.matchCookie(r'^BN[IE]S_.*?='): 21 | return True 22 | 23 | if self.matchContent(r'Barracuda.Networks'): 24 | return True 25 | 26 | return False 27 | -------------------------------------------------------------------------------- /wafw00f/plugins/bekchy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Bekchy (Faydata Technologies Inc.)' 8 | 9 | 10 | def is_waf(self): 11 | # Both signatures are contained within response, so checking for any one of them 12 | # Sometimes I observed that there is an XHR request being being made to submit the 13 | # report data automatically upon page load. In those cases a missing https is causing 14 | # false negatives. 15 | if self.matchContent(r'Bekchy.{0,10}?Access Denied'): 16 | return True 17 | 18 | if self.matchContent(r'bekchy\.com/report'): 19 | return True 20 | 21 | return False 22 | -------------------------------------------------------------------------------- /wafw00f/plugins/beluga.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Beluga CDN (Beluga)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'Beluga')): 12 | return True 13 | 14 | if self.matchCookie(r'^beluga_request_trail='): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/binarysec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'BinarySec (BinarySec)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'BinarySec')): 12 | return True 13 | 14 | if self.matchHeader(('x-binarysec-via', '.+')): 15 | return True 16 | 17 | if self.matchHeader(('x-binarysec-nocache', '.+')): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/bitninja.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'BitNinja (BitNinja)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'Security check by BitNinja'): 12 | return True 13 | 14 | if self.matchContent(r'Visitor anti-robot validation'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/blockdos.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'BlockDoS (BlockDoS)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'blockdos\.net')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/bluedon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Bluedon (Bluedon IST)' 8 | 9 | 10 | def is_waf(self): 11 | # Found sample servers returning 'Server: BDWAF/2.0' 12 | if self.matchHeader(('Server', r'BDWAF')): 13 | return True 14 | 15 | if self.matchContent(r'bluedon web application firewall'): 16 | return True 17 | 18 | return False 19 | -------------------------------------------------------------------------------- /wafw00f/plugins/bulletproof.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'BulletProof Security Pro (AITpro Security)' 8 | 9 | 10 | def is_waf(self): 11 | if not self.matchContent(r'\+?bpsMessage'): 12 | return False 13 | 14 | if not self.matchContent(r'403 Forbidden Error Page'): 15 | return False 16 | 17 | if not self.matchContent(r'If you arrived here due to a search'): 18 | return False 19 | 20 | return True 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/cachefly.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'CacheFly CDN (CacheFly)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('BestCDN', r'Cachefly')): 12 | return True 13 | 14 | if self.matchCookie(r'^cfly_req.*='): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/cachewall.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'CacheWall (Varnish)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'Varnish')): 12 | return True 13 | 14 | if self.matchHeader(('X-Varnish', '.+')): 15 | return True 16 | 17 | if self.matchHeader(('X-Cachewall-Action', '.+?')): 18 | return True 19 | 20 | if self.matchHeader(('X-Cachewall-Reason', '.+?')): 21 | return True 22 | 23 | if self.matchContent(r'security by cachewall'): 24 | return True 25 | 26 | if self.matchContent(r'403 naughty.{0,10}?not nice!'): 27 | return True 28 | 29 | if self.matchContent(r'varnish cache server'): 30 | return True 31 | 32 | return False 33 | -------------------------------------------------------------------------------- /wafw00f/plugins/cdnns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'CdnNS Application Gateway (CdnNs/WdidcNet)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'cdnnswaf application gateway'): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/cerber.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'WP Cerber Security (Cerber Tech)' 8 | 9 | 10 | def is_waf(self): 11 | if not self.matchContent(r'your request looks suspicious or similar to automated'): 12 | return False 13 | 14 | if not self.matchContent(r'our server stopped processing your request'): 15 | return False 16 | 17 | if not self.matchContent(r'We.re sorry.{0,10}?you are not allowed to proceed'): 18 | return False 19 | 20 | if not self.matchContent(r'requests from spam posting software'): 21 | return False 22 | 23 | if not self.matchContent(r'403 Access Forbidden'): 24 | return False 25 | 26 | return True 27 | -------------------------------------------------------------------------------- /wafw00f/plugins/chinacache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'ChinaCache Load Balancer (ChinaCache)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Powered-By-ChinaCache', '.+')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/chuangyu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Chuang Yu Shield (Yunaq)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'www\.365cyd\.com'): 12 | return True 13 | 14 | if self.matchContent(r'help\.365cyd\.com/cyd\-error\-help.html\?code=403'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/ciscoacexml.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'ACE XML Gateway (Cisco)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'ACE XML Gateway')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/cloudbric.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Cloudbric (Penta Security)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'<title>Cloudbric.{0,5}?ERROR!'): 12 | return True 13 | 14 | if self.matchContent(r'Your request was blocked by Cloudbric'): 15 | return True 16 | 17 | if self.matchContent(r'please contact Cloudbric Support'): 18 | return True 19 | 20 | if self.matchContent(r'cloudbric\.zendesk\.com'): 21 | return True 22 | 23 | if self.matchContent(r'Cloudbric Help Center'): 24 | return True 25 | 26 | if self.matchContent(r'malformed request syntax.{0,4}?invalid request message framing.{0,4}?or deceptive request routing'): 27 | return True 28 | 29 | return False 30 | -------------------------------------------------------------------------------- /wafw00f/plugins/cloudflare.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Cloudflare (Cloudflare Inc.)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('server', 'cloudflare')): 12 | return True 13 | 14 | if self.matchHeader(('server', r'cloudflare[-_]nginx')): 15 | return True 16 | 17 | if self.matchHeader(('cf-ray', r'.+?')): 18 | return True 19 | 20 | if self.matchCookie('__cfduid'): 21 | return True 22 | 23 | return False 24 | -------------------------------------------------------------------------------- /wafw00f/plugins/cloudfloordns.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Cloudfloor (Cloudfloor DNS)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'CloudfloorDNS(.WAF)?')): 12 | return True 13 | 14 | if self.matchContent(r'<(title|h\d{1})>CloudfloorDNS.{0,6}?Web Application Firewall Error'): 15 | return True 16 | 17 | if self.matchContent(r'www\.cloudfloordns\.com/contact'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/cloudfront.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Cloudfront (Amazon)' 8 | 9 | 10 | def is_waf(self): 11 | # This is standard detection schema, checking the server header 12 | if self.matchHeader(('Server', 'Cloudfront')): 13 | return True 14 | 15 | # Found samples returning 'Via: 1.1 58bfg7h6fg76h8fg7jhdf2.cloudfront.net (CloudFront)' 16 | if self.matchHeader(('Via', r'([0-9\.]+?)? \w+?\.cloudfront\.net \(Cloudfront\)')): 17 | return True 18 | 19 | # The request token is sent along with this header, eg: 20 | # X-Amz-Cf-Id: sX5QSkbAzSwd-xx3RbJmxYHL3iVNNyXa1UIebDNCshQbHxCjVcWDww== 21 | if self.matchHeader(('X-Amz-Cf-Id', '.+?'), attack=True): 22 | return True 23 | 24 | # This is another reliable fingerprint found on headers 25 | if self.matchHeader(('X-Cache', 'Error from Cloudfront'), attack=True): 26 | return True 27 | 28 | # These fingerprints are found on the blockpage itself 29 | if self.matchContent(r'Generated by cloudfront \(CloudFront\)'): 30 | return True 31 | 32 | return False 33 | -------------------------------------------------------------------------------- /wafw00f/plugins/cloudprotector.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Cloud Protector (Rohde & Schwarz CyberSecurity)' 8 | 9 | def is_waf(self): 10 | if self.matchContent(r'Cloud Protector.*?by Rohde.{3,8}?Schwarz Cybersecurity'): 11 | return True 12 | 13 | if self.matchContent(r"<a href='https?:\/\/(?:www\.)?cloudprotector\.com\/'>R.{1,6}?S.Cloud Protector"): 14 | return True 15 | 16 | return False 17 | -------------------------------------------------------------------------------- /wafw00f/plugins/comodo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Comodo cWatch (Comodo CyberSecurity)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'Protected by COMODO WAF(.+)?')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/crawlprotect.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'CrawlProtect (Jean-Denis Brun)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie(r'^crawlprotecttag='): 12 | return True 13 | 14 | if self.matchContent(r'<title>crawlprotect'): 15 | return True 16 | 17 | if self.matchContent(r'this site is protected by crawlprotect'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/ddosguard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'DDoS-GUARD (DDOS-GUARD CORP.)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie(r'^__ddg1.*?='): 12 | return True 13 | 14 | if self.matchCookie(r'^__ddg2.*?='): 15 | return True 16 | 17 | if self.matchCookie(r'^__ddgid.*?='): 18 | return True 19 | 20 | if self.matchCookie(r'^__ddgmark.*?='): 21 | return True 22 | 23 | if self.matchHeader(('Server', 'ddos-guard')): 24 | return True 25 | 26 | return False 27 | -------------------------------------------------------------------------------- /wafw00f/plugins/denyall.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'DenyALL (Rohde & Schwarz CyberSecurity)' 8 | 9 | 10 | def is_waf(self): 11 | if not self.matchStatus(200): 12 | return False 13 | 14 | if not self.matchReason('Condition Intercepted'): 15 | return False 16 | 17 | return True 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/distil.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Distil (Distil Networks)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'cdn\.distilnetworks\.com/images/anomaly\.detected\.png'): 12 | return True 13 | 14 | if self.matchContent(r'distilCaptchaForm'): 15 | return True 16 | 17 | if self.matchContent(r'distilCallbackGuard'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/dosarrest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'DOSarrest (DOSarrest Internet Security)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-DIS-Request-ID', '.+')): 12 | return True 13 | 14 | # Found samples of DOSArrest returning 'Server: DoSArrest/3.5' 15 | if self.matchHeader(('Server', r'DOSarrest(.*)?')): 16 | return True 17 | 18 | return False 19 | -------------------------------------------------------------------------------- /wafw00f/plugins/dotdefender.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'DotDefender (Applicure Technologies)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-dotDefender-denied', r'.+?'), attack=True): 12 | return True 13 | 14 | if self.matchContent(r'dotdefender blocked your request'): 15 | return True 16 | 17 | if self.matchContent(r'Applicure is the leading provider of web application security'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/dynamicweb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'DynamicWeb Injection Check (DynamicWeb)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-403-Status-By', r'dw.inj.check'), attack=True): 12 | return True 13 | 14 | if self.matchContent(r'by dynamic check(.{0,10}?module)?'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/edgecast.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Edgecast (Verizon Digital Media)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'^ECD(.+)?')): 12 | return True 13 | 14 | if self.matchHeader(('Server', r'^ECS(.*)?')): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/eisoo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Eisoo Cloud Firewall (Eisoo)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'EisooWAF(\-AZURE)?/?')): 12 | return True 13 | 14 | if self.matchContent(r'<link.{0,10}?href=\"/eisoo\-firewall\-block\.css'): 15 | return True 16 | 17 | if self.matchContent(r'www\.eisoo\.com'): 18 | return True 19 | 20 | if self.matchContent(r'© \d{4} Eisoo Inc'): 21 | return True 22 | 23 | return False 24 | -------------------------------------------------------------------------------- /wafw00f/plugins/envoy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Copyright (C) 2022, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Envoy (EnvoyProxy)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('server', 'envoy')): 12 | return True 13 | 14 | if self.matchHeader(('x-envoy-upstream-service-time', '.+')): 15 | return True 16 | 17 | if self.matchHeader(('x-envoy-downstream-service-cluster', '.+')): 18 | return True 19 | 20 | if self.matchHeader(('x-envoy-downstream-service-node', '.+')): 21 | return True 22 | 23 | if self.matchHeader(('x-envoy-external-address', '.+')): 24 | return True 25 | 26 | if self.matchHeader(('x-envoy-force-trace', '.+')): 27 | return True 28 | 29 | if self.matchHeader(('x-envoy-internal', '.+')): 30 | return True 31 | 32 | if self.matchHeader(('x-envoy-original-dst-host', '.+')): 33 | return True 34 | 35 | if self.matchHeader(('x-envoy-original-path', '.+')): 36 | return True 37 | 38 | if self.matchHeader(('x-envoy-local-overloaded', '.+')): 39 | return True 40 | 41 | return False 42 | -------------------------------------------------------------------------------- /wafw00f/plugins/expressionengine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Expression Engine (EllisLab)' 8 | 9 | 10 | def is_waf(self): 11 | # I have seen some sites use a tracking header and sets a cookie upon authentication 12 | # 'Set-Cookie: _exp_tracking=rufyhweiuitefgcxyniercyft5-6dctuxeygfr' 13 | if self.matchCookie(r'^exp_track.+?='): 14 | return True 15 | 16 | # There are traces found where cookie is returning values like: 17 | # Set-Cookie: exp_last_query=834y8d73y94d8g983u4shn8u4shr3uh3 18 | # Set-Cookie: exp_last_id=b342b432b1a876r8 19 | if self.matchCookie(r'^exp_last_.+?=', attack=True): 20 | return True 21 | 22 | # In-page fingerprints vary a lot in different sites. Hence these are not quite reliable. 23 | if self.matchContent(r'invalid get data'): 24 | return True 25 | 26 | return False 27 | -------------------------------------------------------------------------------- /wafw00f/plugins/f5bigipapm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'BIG-IP AP Manager (F5 Networks)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | if check_schema_03(self): 18 | return True 19 | 20 | return False 21 | 22 | 23 | def check_schema_01(self): 24 | if not self.matchCookie('^LastMRH_Session'): 25 | return False 26 | 27 | if not self.matchCookie('^MRHSession'): 28 | return False 29 | 30 | return True 31 | 32 | 33 | def check_schema_02(self): 34 | if not self.matchCookie('^MRHSession'): 35 | return False 36 | 37 | if not self.matchHeader(('Server', r'Big([-_])?IP'), attack=True): 38 | return False 39 | 40 | return True 41 | 42 | 43 | def check_schema_03(self): 44 | if self.matchCookie('^F5_fullWT'): 45 | return True 46 | 47 | if self.matchCookie('^F5_fullWT'): 48 | return True 49 | 50 | if self.matchCookie('^F5_HT_shrinked'): 51 | return True 52 | 53 | return False 54 | -------------------------------------------------------------------------------- /wafw00f/plugins/f5bigipasm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'BIG-IP AppSec Manager (F5 Networks)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if self.matchCookie(r'^TS.+?'): 15 | return True 16 | 17 | return False 18 | 19 | 20 | def check_schema_01(self): 21 | if not self.matchContent('the requested url was rejected'): 22 | return False 23 | 24 | if not self.matchContent('please consult with your administrator'): 25 | return False 26 | 27 | return True 28 | -------------------------------------------------------------------------------- /wafw00f/plugins/f5bigipltm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'BIG-IP Local Traffic Manager (F5 Networks)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie('^bigipserver'): 12 | return True 13 | 14 | if self.matchHeader(('X-Cnection', 'close'), attack=True): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/f5firepass.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'FirePass (F5 Networks)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | return False 18 | 19 | 20 | def check_schema_01(self): 21 | if not self.matchCookie('^VHOST'): 22 | return False 23 | 24 | if not self.matchHeader(('Location', r'\/my\.logon\.php3')): 25 | return False 26 | 27 | return True 28 | 29 | 30 | def check_schema_02(self): 31 | if not self.matchCookie(r'^F5_fire.+?'): 32 | return False 33 | 34 | if not self.matchCookie('^F5_passid_shrinked'): 35 | return False 36 | 37 | return True 38 | -------------------------------------------------------------------------------- /wafw00f/plugins/f5trafficshield.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Trafficshield (F5 Networks)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie('^ASINFO='): 12 | return True 13 | 14 | if self.matchHeader(('Server', 'F5-TrafficShield')): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/fastly.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Fastly (Fastly CDN)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-Fastly-Request-ID', r'\w+')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/fortigate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2023, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'FortiGate (Fortinet)' 8 | 9 | def is_waf(self): 10 | if check_schema_01(self): 11 | return True 12 | 13 | if check_schema_02(self): 14 | return True 15 | 16 | return False 17 | 18 | def check_schema_01(self): 19 | if not self.matchContent('//globalurl.fortinet.net'): 20 | return False 21 | 22 | if not self.matchContent('FortiGate Application Control'): 23 | return False 24 | 25 | return True 26 | 27 | def check_schema_02(self): 28 | if not self.matchContent('Web Application Firewall'): 29 | return False 30 | 31 | if not self.matchContent('Event ID'): 32 | return False 33 | 34 | if not self.matchContent('//globalurl.fortinet.net'): 35 | return False 36 | 37 | return True 38 | -------------------------------------------------------------------------------- /wafw00f/plugins/fortiguard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2023, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'FortiGuard (Fortinet)' 8 | 9 | def is_waf(self): 10 | if check_schema(self): 11 | return True 12 | 13 | return False 14 | 15 | def check_schema(self): 16 | if not self.matchContent('FortiGuard Intrusion Prevention'): 17 | return False 18 | 19 | if not self.matchContent('//globalurl.fortinet.net'): 20 | return False 21 | 22 | if not self.matchContent('<title>Web Filter Violation'): 23 | return False 24 | 25 | return True 26 | -------------------------------------------------------------------------------- /wafw00f/plugins/fortiweb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'FortiWeb (Fortinet)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | return False 18 | 19 | 20 | def check_schema_01(self): 21 | if self.matchCookie(r'^FORTIWAFSID='): 22 | return True 23 | 24 | if self.matchContent('.fgd_icon'): 25 | return True 26 | 27 | return False 28 | 29 | 30 | def check_schema_02(self): 31 | if not self.matchContent('fgd_icon'): 32 | return False 33 | 34 | if not self.matchContent('web.page.blocked'): 35 | return False 36 | 37 | if not self.matchContent('url'): 38 | return False 39 | 40 | if not self.matchContent('attack.id'): 41 | return False 42 | 43 | if not self.matchContent('message.id'): 44 | return False 45 | 46 | if not self.matchContent('client.ip'): 47 | return False 48 | 49 | return True 50 | -------------------------------------------------------------------------------- /wafw00f/plugins/frontdoor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Azure Front Door (Microsoft)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-Azure-Ref', '.+?')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/gcparmor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Google Cloud App Armor (Google Cloud)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Via', '1.1 google')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/godaddy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'GoDaddy Website Protection (GoDaddy)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'GoDaddy (security|website firewall)'): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/greywizard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Greywizard (Grey Wizard)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'greywizard')): 12 | return True 13 | 14 | if self.matchContent(r'<(title|h\d{1})>Grey Wizard'): 15 | return True 16 | 17 | if self.matchContent(r'contact the website owner or Grey Wizard'): 18 | return True 19 | 20 | if self.matchContent(r'We.ve detected attempted attack or non standard traffic from your ip address'): 21 | return True 22 | 23 | return False 24 | -------------------------------------------------------------------------------- /wafw00f/plugins/huaweicloud.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Huawei Cloud Firewall (Huawei)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie(r'^HWWAFSESID='): 12 | return True 13 | 14 | if self.matchHeader(('Server', r'HuaweiCloudWAF')): 15 | return True 16 | 17 | if self.matchContent(r'hwclouds\.com'): 18 | return True 19 | 20 | if self.matchContent(r'hws_security@'): 21 | return True 22 | 23 | return False 24 | -------------------------------------------------------------------------------- /wafw00f/plugins/hyperguard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'HyperGuard (Art of Defense)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie('^WODSESSION='): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/ibmdatapower.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'DataPower (IBM)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-Backside-Transport', r'(OK|FAIL)')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/imunify360.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Imunify360 (CloudLinux)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'imunify360.{0,10}?')): 12 | return True 13 | 14 | if self.matchContent(r'protected.by.{0,10}?imunify360'): 15 | return True 16 | 17 | if self.matchContent(r'powered.by.{0,10}?imunify360'): 18 | return True 19 | 20 | if self.matchContent(r'imunify360.preloader'): 21 | return True 22 | 23 | return False 24 | -------------------------------------------------------------------------------- /wafw00f/plugins/incapsula.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Incapsula (Imperva Inc.)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie(r'^incap_ses.*?='): 12 | return True 13 | 14 | if self.matchCookie(r'^visid_incap.*?='): 15 | return True 16 | 17 | if self.matchContent(r'incapsula incident id'): 18 | return True 19 | 20 | if self.matchContent(r'powered by incapsula'): 21 | return True 22 | 23 | if self.matchContent(r'/_Incapsula_Resource'): 24 | return True 25 | 26 | return False 27 | -------------------------------------------------------------------------------- /wafw00f/plugins/indusguard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'IndusGuard (Indusface)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'IF_WAF')): 12 | return True 13 | 14 | if self.matchContent(r'This website is secured against online attacks. Your request was blocked'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/instartdx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Instart DX (Instart Logic)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | return False 18 | 19 | 20 | def check_schema_01(self): 21 | if self.matchHeader(('X-Instart-Request-ID', '.+')): 22 | return True 23 | 24 | if self.matchHeader(('X-Instart-Cache', '.+')): 25 | return True 26 | 27 | if self.matchHeader(('X-Instart-WL', '.+')): 28 | return True 29 | 30 | return False 31 | 32 | 33 | def check_schema_02(self): 34 | if not self.matchContent(r'the requested url was rejected'): 35 | return False 36 | 37 | if not self.matchContent(r'please consult with your administrator'): 38 | return False 39 | 40 | if not self.matchContent(r'your support id is'): 41 | return False 42 | 43 | return True 44 | -------------------------------------------------------------------------------- /wafw00f/plugins/isaserver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'ISA Server (Microsoft)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'The.{0,10}?(isa.)?server.{0,10}?denied the specified uniform resource locator \(url\)'): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/janusec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Janusec Application Gateway (Janusec)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'janusec application gateway'): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/jiasule.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Jiasule (Jiasule)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'jiasule\-waf')): 12 | return True 13 | 14 | if self.matchCookie(r'^jsl_tracking(.+)?='): 15 | return True 16 | 17 | if self.matchCookie(r'__jsluid='): 18 | return True 19 | 20 | if self.matchContent(r'notice\-jiasule'): 21 | return True 22 | 23 | if self.matchContent(r'static\.jiasule\.com'): 24 | return True 25 | 26 | return False 27 | -------------------------------------------------------------------------------- /wafw00f/plugins/kemp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Kemp LoadMaster (Progress Software)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-ServedBy', 'KEMP-LM')) and \ 12 | self.matchStatus(403) and \ 13 | self.matchContent(r'<title>403 Forbidden'): 14 | return True 15 | 16 | return False 17 | -------------------------------------------------------------------------------- /wafw00f/plugins/keycdn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'KeyCDN (KeyCDN)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'KeyCDN')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/knownsec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'KS-WAF (KnownSec)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'/ks[-_]waf[-_]error\.png'): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/kona.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | 4 | #!/usr/bin/env python3 5 | ''' 6 | Copyright (C) 2024, WAFW00F Developers. 7 | See the LICENSE file for copying permission. 8 | ''' 9 | 10 | NAME = 'Kona SiteDefender (Akamai)' 11 | 12 | 13 | def is_waf(self): 14 | if self.matchHeader(('Server', 'AkamaiGHost')): 15 | return True 16 | 17 | if self.matchHeader(('Server', 'AkamaiGHost'), attack=True) : 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/limelight.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'LimeLight CDN (LimeLight)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie(r'^limelight'): 12 | return True 13 | 14 | if self.matchCookie(r'^l[mg]_sessid='): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/litespeed.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'LiteSpeed (LiteSpeed Technologies)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | return False 18 | 19 | 20 | def check_schema_01(self): 21 | if not self.matchHeader(('Server', 'LiteSpeed')): 22 | return False 23 | 24 | if not self.matchStatus(403): 25 | return False 26 | 27 | return True 28 | 29 | 30 | def check_schema_02(self): 31 | if self.matchContent(r'Proudly powered by litespeed web server'): 32 | return True 33 | 34 | if self.matchContent(r'www\.litespeedtech\.com/error\-page'): 35 | return True 36 | 37 | return False 38 | -------------------------------------------------------------------------------- /wafw00f/plugins/malcare.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Malcare (Inactiv)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'firewall.{0,15}?powered.by.{0,15}?malcare.{0,15}?pro'): 12 | return True 13 | 14 | if self.matchContent('blocked because of malicious activities'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/maxcdn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'MaxCDN (MaxCDN)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-CDN', r'maxcdn')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/missioncontrol.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Mission Control Shield (Mission Control)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'Mission Control Application Shield')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/modsecurity.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'ModSecurity (SpiderLabs)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | if check_schema_03(self): 18 | return True 19 | 20 | return False 21 | 22 | 23 | def check_schema_01(self): 24 | if self.matchHeader(('Server', r'(mod_security|Mod_Security|NOYB)')): 25 | return True 26 | 27 | if self.matchContent(r'This error was generated by Mod.?Security'): 28 | return True 29 | 30 | if self.matchContent(r'rules of the mod.security.module'): 31 | return True 32 | 33 | if self.matchContent(r'mod.security.rules triggered'): 34 | return True 35 | 36 | if self.matchContent(r'Protected by Mod.?Security'): 37 | return True 38 | 39 | if self.matchContent(r'/modsecurity[\-_]errorpage/'): 40 | return True 41 | 42 | if self.matchContent(r'modsecurity iis'): 43 | return True 44 | 45 | return False 46 | 47 | 48 | def check_schema_02(self): 49 | if not self.matchReason('ModSecurity Action'): 50 | return False 51 | 52 | if not self.matchStatus(403): 53 | return False 54 | 55 | return True 56 | 57 | 58 | def check_schema_03(self): 59 | if not self.matchReason('ModSecurity Action'): 60 | return False 61 | 62 | if not self.matchStatus(406): 63 | return False 64 | 65 | return True 66 | -------------------------------------------------------------------------------- /wafw00f/plugins/naxsi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'NAXSI (NBS Systems)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-Data-Origin', r'^naxsi(.+)?')): 12 | return True 13 | 14 | if self.matchHeader(('Server', r'naxsi(.+)?')): 15 | return True 16 | 17 | if self.matchContent(r'blocked by naxsi'): 18 | return True 19 | 20 | if self.matchContent(r'naxsi blocked information'): 21 | return True 22 | 23 | return False 24 | -------------------------------------------------------------------------------- /wafw00f/plugins/nemesida.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Nemesida (PentestIt)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'@?nemesida(\-security)?\.com'): 12 | return True 13 | 14 | if self.matchContent(r'Suspicious activity detected.{0,10}?Access to the site is blocked'): 15 | return True 16 | 17 | if self.matchContent(r'nwaf@'): 18 | return True 19 | 20 | if self.matchStatus(222): 21 | return True 22 | 23 | return False 24 | -------------------------------------------------------------------------------- /wafw00f/plugins/netcontinuum.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'NetContinuum (Barracuda Networks)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie(r'^NCI__SessionId='): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/netscaler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'NetScaler AppFirewall (Citrix Systems)' 8 | 9 | 10 | def is_waf(self): 11 | # This header can be obtained without attack mode 12 | if self.matchHeader(('Via', r'NS\-CACHE')): 13 | return True 14 | 15 | # Cookies are set only when someone is authenticated. 16 | # Not much reliable since wafw00f isn't authenticating. 17 | if self.matchCookie(r'^(ns_af=|citrix_ns_id|NSC_)'): 18 | return True 19 | 20 | if self.matchContent(r'(NS Transaction|AppFW Session) id'): 21 | return True 22 | 23 | if self.matchContent(r'Violation Category.{0,5}?APPFW_'): 24 | return True 25 | 26 | if self.matchContent(r'Citrix\|NetScaler'): 27 | return True 28 | 29 | # Reliable but not all servers return this header 30 | if self.matchHeader(('Cneonction', r'^(keep alive|close)'), attack=True): 31 | return True 32 | 33 | if self.matchHeader(('nnCoection', r'^(keep alive|close)'), attack=True): 34 | return True 35 | 36 | return False 37 | -------------------------------------------------------------------------------- /wafw00f/plugins/nevisproxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'NevisProxy (AdNovum)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie(r'^Navajo'): 12 | return True 13 | 14 | if self.matchCookie(r'^NP_ID'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/newdefend.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Newdefend (NewDefend)' 8 | 9 | 10 | def is_waf(self): 11 | # This header can be obtained without attack mode 12 | # Most reliable fingerprint 13 | if self.matchHeader(('Server', 'Newdefend')): 14 | return True 15 | 16 | # Reliable ones within blockpage 17 | if self.matchContent(r'www\.newdefend\.com/feedback'): 18 | return True 19 | 20 | if self.matchContent(r'/nd\-block/'): 21 | return True 22 | 23 | return False 24 | -------------------------------------------------------------------------------- /wafw00f/plugins/nexusguard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'NexusGuard Firewall (NexusGuard)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'Powered by Nexusguard'): 12 | return True 13 | 14 | if self.matchContent(r'nexusguard\.com/wafpage/.+#\d{3};'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/ninja.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'NinjaFirewall (NinTechNet)' 8 | 9 | 10 | def is_waf(self): 11 | if not self.matchContent(r'NinjaFirewall.{0,10}?\d{3}.forbidden'): 12 | return False 13 | 14 | if not self.matchContent(r'For security reasons?.{0,10}?it was blocked and logged'): 15 | return False 16 | 17 | return True 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/nsfocus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'NSFocus (NSFocus Global Inc.)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'NSFocus')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/nullddos.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'NullDDoS Protection (NullDDoS)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'NullDDoS(.System)?')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/onmessage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'OnMessage Shield (BlackBaud)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-Engine', 'onMessage Shield')): 12 | return True 13 | 14 | if self.matchContent(r'Blackbaud K\-12 conducts routine maintenance'): 15 | return True 16 | 17 | if self.matchContent(r'onMessage SHEILD'): 18 | return True 19 | 20 | if self.matchContent(r'maintenance\.blackbaud\.com'): 21 | return True 22 | 23 | if self.matchContent(r'status\.blackbaud\.com'): 24 | return True 25 | 26 | return False 27 | -------------------------------------------------------------------------------- /wafw00f/plugins/openresty.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Open-Resty Lua Nginx (FLOSS)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | return False 18 | 19 | 20 | def check_schema_01(self): 21 | if not self.matchHeader(('Server', r'^openresty/[0-9\.]+?')): 22 | return False 23 | 24 | if not self.matchStatus(403): 25 | return False 26 | 27 | return True 28 | 29 | 30 | def check_schema_02(self): 31 | if not self.matchContent(r'openresty/[0-9\.]+?'): 32 | return False 33 | 34 | if not self.matchStatus(406): 35 | return False 36 | 37 | return True 38 | -------------------------------------------------------------------------------- /wafw00f/plugins/oraclecloud.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Oracle Cloud (Oracle)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'<title>fw_error_www'): 12 | return True 13 | 14 | if self.matchContent(r'src=\"/oralogo_small\.gif\"'): 15 | return True 16 | 17 | if self.matchContent(r'www\.oracleimg\.com/us/assets/metrics/ora_ocom\.js'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/paloalto.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Palo Alto Next Gen Firewall (Palo Alto Networks)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'Download of virus.spyware blocked'): 12 | return True 13 | 14 | if self.matchContent(r'Palo Alto Next Generation Security Platform'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/pentawaf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'PentaWAF (Global Network Services)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'PentaWaf(/[0-9\.]+)?')): 12 | return True 13 | 14 | if self.matchContent(r'Penta.?Waf/[0-9\.]+?.server'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/perimeterx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'PerimeterX (PerimeterX)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'www\.perimeterx\.(com|net)/whywasiblocked'): 12 | return True 13 | 14 | if self.matchContent(r'client\.perimeterx\.(net|com)'): 15 | return True 16 | 17 | if self.matchContent(r'denied because we believe you are using automation tools'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/pksec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'pkSecurity IDS (pkSec)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | return False 18 | 19 | 20 | def check_schema_01(self): 21 | if self.matchContent(r'pk.?Security.?Module'): 22 | return True 23 | 24 | if self.matchContent(r'Security.Alert'): 25 | return True 26 | 27 | return False 28 | 29 | 30 | def check_schema_02(self): 31 | if not self.matchContent(r'As this could be a potential hack attack'): 32 | return False 33 | 34 | if not self.matchContent(r'A safety critical (call|request) was (detected|discovered) and blocked'): 35 | return False 36 | 37 | if not self.matchContent(r'maximum number of reloads per minute and prevented access'): 38 | return False 39 | 40 | return True 41 | -------------------------------------------------------------------------------- /wafw00f/plugins/powercdn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'PowerCDN (PowerCDN)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Via', r'(.*)?powercdn.com(.*)?')): 12 | return True 13 | 14 | if self.matchHeader(('X-Cache', r'(.*)?powercdn.com(.*)?')): 15 | return True 16 | 17 | if self.matchHeader(('X-CDN', r'PowerCDN')): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/profense.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Profense (ArmorLogic)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'Profense')): 12 | return True 13 | 14 | if self.matchCookie(r'^PLBSID='): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/ptaf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'PT Application Firewall (Positive Technologies)' 8 | 9 | 10 | def is_waf(self): 11 | if not self.matchContent(r'<h1.{0,10}?Forbidden'): 12 | return False 13 | 14 | if not self.matchContent(r'<pre>Request.ID:.{0,10}?\d{4}\-(\d{2})+.{0,35}?pre>'): 15 | return False 16 | 17 | return True 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/puhui.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Puhui (Puhui)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'Puhui[\-_]?WAF')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/qcloud.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Qcloud (Tencent Cloud)' 8 | 9 | 10 | def is_waf(self): 11 | if not self.matchContent(r'腾讯云Web应用防火墙'): 12 | return False 13 | 14 | if not self.matchStatus(403): 15 | return False 16 | 17 | return True 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/qiniu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Qiniu (Qiniu CDN)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-Qiniu-CDN', r'\d+?')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/qrator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Qrator (Qrator)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'QRATOR')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/radware.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'AppWall (Radware)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | return False 18 | 19 | 20 | def check_schema_01(self): 21 | if self.matchContent(r'CloudWebSec\.radware\.com'): 22 | return True 23 | 24 | if self.matchHeader(('X-SL-CompState', '.+')): 25 | return True 26 | 27 | return False 28 | 29 | 30 | def check_schema_02(self): 31 | if not self.matchContent(r'because we have detected unauthorized activity'): 32 | return False 33 | 34 | if not self.matchContent(r'<title>Unauthorized Request Blocked'): 35 | return False 36 | 37 | if not self.matchContent(r'if you believe that there has been some mistake'): 38 | return False 39 | 40 | if not self.matchContent(r'\?Subject=Security Page.{0,10}?Case Number'): 41 | return False 42 | 43 | return True 44 | -------------------------------------------------------------------------------- /wafw00f/plugins/reblaze.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Reblaze (Reblaze)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | return False 18 | 19 | 20 | def check_schema_01(self): 21 | if self.matchCookie(r'^rbzid'): 22 | return True 23 | 24 | if self.matchHeader(('Server', 'Reblaze Secure Web Gateway')): 25 | return True 26 | 27 | return False 28 | 29 | 30 | def check_schema_02(self): 31 | if not self.matchContent(r'current session has been terminated'): 32 | return False 33 | 34 | if not self.matchContent(r'do not hesitate to contact us'): 35 | return False 36 | 37 | if not self.matchContent(r'access denied \(\d{3}\)'): 38 | return False 39 | 40 | return True 41 | -------------------------------------------------------------------------------- /wafw00f/plugins/rsfirewall.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'RSFirewall (RSJoomla!)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'com_rsfirewall_(\d{3}_forbidden|event)?'): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/rvmode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'RequestValidationMode (Microsoft)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'Request Validation has detected a potentially dangerous client input'): 12 | return True 13 | 14 | if self.matchContent(r'ASP\.NET has detected data in the request'): 15 | return True 16 | 17 | if self.matchContent(r'HttpRequestValidationException'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/sabre.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Sabre Firewall (Sabre)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'dxsupport\.sabre\.com'): 12 | return True 13 | 14 | if check_schema_01(self): 15 | return True 16 | 17 | return False 18 | 19 | 20 | def check_schema_01(self): 21 | if not self.matchContent(r'<title>Application Firewall Error'): 22 | return False 23 | 24 | if not self.matchContent(r'add some important details to the email for us to investigate'): 25 | return False 26 | 27 | return True 28 | -------------------------------------------------------------------------------- /wafw00f/plugins/safe3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Safe3 Web Firewall (Safe3)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'Safe3 Web Firewall')): 12 | return True 13 | 14 | if self.matchHeader(('X-Powered-By', r'Safe3WAF/[\.0-9]+?')): 15 | return True 16 | 17 | if self.matchContent(r'Safe3waf/[0-9\.]+?'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/safedog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Safedog (SafeDog)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie(r'^safedog\-flow\-item='): 12 | return True 13 | 14 | if self.matchHeader(('Server', 'Safedog')): 15 | return True 16 | 17 | if self.matchContent(r'safedogsite/broswer_logo\.jpg'): 18 | return True 19 | 20 | if self.matchContent(r'404\.safedog\.cn/sitedog_stat.html'): 21 | return True 22 | 23 | if self.matchContent(r'404\.safedog\.cn/images/safedogsite/head\.png'): 24 | return True 25 | 26 | return False 27 | -------------------------------------------------------------------------------- /wafw00f/plugins/safeline.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Safeline (Chaitin Tech.)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'safeline|<!\-\-\sevent id:'): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/secking.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'SecKing (SecKing)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'secking(.?waf)?')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/secupress.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'SecuPress WP Security (SecuPress)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'<(title|h\d{1})>SecuPress'): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/secureentry.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Secure Entry (United Security Providers)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'Secure Entry Server')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/secureiis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'eEye SecureIIS (BeyondTrust)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'SecureIIS is an internet security application'): 12 | return True 13 | 14 | if self.matchContent(r'Download SecureIIS Personal Edition'): 15 | return True 16 | 17 | if self.matchContent(r'https?://www\.eeye\.com/Secure\-?IIS'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/securesphere.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'SecureSphere (Imperva Inc.)' 8 | 9 | 10 | def is_waf(self): 11 | if not self.matchContent(r'<(title|h2)>Error'): 12 | return False 13 | 14 | if not self.matchContent(r'The incident ID is'): 15 | return False 16 | 17 | if not self.matchContent(r"This page can't be displayed"): 18 | return False 19 | 20 | if not self.matchContent(r'Contact support for additional information'): 21 | return False 22 | 23 | return True 24 | -------------------------------------------------------------------------------- /wafw00f/plugins/senginx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'SEnginx (Neusoft)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'SENGINX\-ROBOT\-MITIGATION'): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/serverdefender.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'ServerDefender VP (Port80 Software)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-Pint', r'p(ort\-)?80')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/shadowd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Shadow Daemon (Zecure)' 8 | 9 | 10 | def is_waf(self): 11 | if not self.matchContent(r"<h\d{1}>\d{3}.forbidden<.h\d{1}>"): 12 | return False 13 | 14 | if not self.matchContent(r"request forbidden by administrative rules"): 15 | return False 16 | 17 | return True 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/shieldon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2021, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Shieldon Firewall (Shieldon.io)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | if check_schema_03(self): 18 | return True 19 | 20 | if self.matchHeader((r'[Xx]-[Pp]rotected-[Bb]y', 'shieldon.io')): 21 | return True 22 | 23 | return False 24 | 25 | 26 | def check_schema_01(self): 27 | if not self.matchContent('Please solve CAPTCHA'): 28 | return False 29 | 30 | if not self.matchContent('shieldon_captcha'): 31 | return False 32 | 33 | if not self.matchContent('Unusual behavior detected'): 34 | return False 35 | 36 | if not self.matchContent('status-user-info'): 37 | return False 38 | 39 | return True 40 | 41 | 42 | def check_schema_02(self): 43 | if not self.matchContent('Access denied'): 44 | return False 45 | 46 | if not self.matchContent('The IP address you are using has been blocked.'): 47 | return False 48 | 49 | if not self.matchContent('status-user-info'): 50 | return False 51 | 52 | return True 53 | 54 | 55 | def check_schema_03(self): 56 | if not self.matchContent('Please line up'): 57 | return False 58 | 59 | if not self.matchContent('This page is limiting the number of people online. Please wait a moment.'): 60 | return False 61 | 62 | return True 63 | -------------------------------------------------------------------------------- /wafw00f/plugins/shieldsecurity.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Shield Security (One Dollar Plugin)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r"You were blocked by the Shield"): 12 | return True 13 | 14 | if self.matchContent(r"remaining transgression\(s\) against this site"): 15 | return True 16 | 17 | if self.matchContent(r"Something in the URL.{0,5}?Form or Cookie data wasn\'t appropriate"): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/siteground.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'SiteGround (SiteGround)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r"Our system thinks you might be a robot!"): 12 | return True 13 | 14 | if self.matchContent(r'access is restricted due to a security rule'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/siteguard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'SiteGuard (Sakura Inc.)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r"Powered by SiteGuard"): 12 | return True 13 | 14 | if self.matchContent(r'The server refuse to browse the page'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/sitelock.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Sitelock (TrueShield)' 8 | 9 | # Well this is confusing, Sitelock itself uses Incapsula from Imperva 10 | # So the fingerprints obtained on blockpage are similar to those of Incapsula. 11 | 12 | def is_waf(self): 13 | if self.matchContent(r"SiteLock will remember you"): 14 | return True 15 | 16 | if self.matchContent(r"Sitelock is leader in Business Website Security Services"): 17 | return True 18 | 19 | if self.matchContent(r"sitelock[_\-]shield([_\-]logo|[\-_]badge)?"): 20 | return True 21 | 22 | if self.matchContent(r'SiteLock incident ID'): 23 | return True 24 | 25 | return False 26 | -------------------------------------------------------------------------------- /wafw00f/plugins/sonicwall.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'SonicWall (Dell)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'SonicWALL')): 12 | return True 13 | 14 | if self.matchContent(r"<(title|h\d{1})>Web Site Blocked"): 15 | return True 16 | 17 | if self.matchContent(r'\+?nsa_banner'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/sophos.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'UTM Web Protection (Sophos)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | return False 18 | 19 | 20 | def check_schema_01(self): 21 | if self.matchContent(r'www\.sophos\.com'): 22 | return True 23 | 24 | if self.matchContent(r'Powered by.?(Sophos)? UTM Web Protection'): 25 | return True 26 | 27 | return False 28 | 29 | 30 | def check_schema_02(self): 31 | if not self.matchContent(r'<title>Access to the requested URL was blocked'): 32 | return False 33 | 34 | if not self.matchContent(r'Access to the requested URL was blocked'): 35 | return False 36 | 37 | if not self.matchContent(r'incident was logged with the following log identifier'): 38 | return False 39 | 40 | if not self.matchContent(r'Inbound Anomaly Score exceeded'): 41 | return False 42 | 43 | if not self.matchContent(r'Your cache administrator is'): 44 | return False 45 | 46 | return True 47 | -------------------------------------------------------------------------------- /wafw00f/plugins/squarespace.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Squarespace (Squarespace)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'Squarespace')): 12 | return True 13 | 14 | if self.matchCookie(r'^SS_ANALYTICS_ID='): 15 | return True 16 | 17 | if self.matchCookie(r'^SS_MATTR='): 18 | return True 19 | 20 | if self.matchCookie(r'^SS_MID='): 21 | return True 22 | 23 | if self.matchCookie(r'SS_CVT='): 24 | return True 25 | 26 | if self.matchContent(r'status\.squarespace\.com'): 27 | return True 28 | 29 | if self.matchContent(r'BRICK\-\d{2}'): 30 | return True 31 | 32 | return False 33 | -------------------------------------------------------------------------------- /wafw00f/plugins/squidproxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'SquidProxy IDS (SquidProxy)' 8 | 9 | 10 | def is_waf(self): 11 | if not self.matchHeader(('Server', r'squid(/[0-9\.]+)?')): 12 | return False 13 | 14 | if not self.matchContent(r'Access control configuration prevents your request'): 15 | return False 16 | 17 | return True 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/stackpath.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'StackPath (StackPath)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | return False 18 | 19 | 20 | def check_schema_01(self): 21 | if self.matchContent(r'<title>StackPath[^<]+'): 22 | return True 23 | 24 | if self.matchContent(r'Protected by ]+>StackPath'): 25 | return True 26 | 27 | return False 28 | 29 | 30 | def check_schema_02(self): 31 | if not self.matchContent(r"is using a security service for protection against online attacks"): 32 | return False 33 | 34 | if not self.matchContent(r'An action has triggered the service and blocked your request'): 35 | return False 36 | 37 | return True 38 | -------------------------------------------------------------------------------- /wafw00f/plugins/sucuri.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Sucuri CloudProxy (Sucuri Inc.)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-Sucuri-ID', r'.+?')): 12 | return True 13 | 14 | if self.matchHeader(('X-Sucuri-Cache', r'.+?')): 15 | return True 16 | 17 | if self.matchHeader(('Server', r'Sucuri(\-Cloudproxy)?')): 18 | return True 19 | 20 | if self.matchHeader(('X-Sucuri-Block', r'.+?'), attack=True): 21 | return True 22 | 23 | if self.matchContent(r"Access Denied.{0,6}?Sucuri Website Firewall"): 24 | return True 25 | 26 | if self.matchContent(r"Sucuri WebSite Firewall.{0,6}?(CloudProxy)?.{0,6}?Access Denied"): 27 | return True 28 | 29 | if self.matchContent(r"sucuri\.net/privacy\-policy"): 30 | return True 31 | 32 | if self.matchContent(r"cdn\.sucuri\.net/sucuri[-_]firewall[-_]block\.css"): 33 | return True 34 | 35 | if self.matchContent(r'cloudproxy@sucuri\.net'): 36 | return True 37 | 38 | return False 39 | -------------------------------------------------------------------------------- /wafw00f/plugins/tencent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Tencent Cloud Firewall (Tencent Technologies)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'waf\.tencent\-?cloud\.com/'): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/teros.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Teros (Citrix Systems)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie(r'^st8id='): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/transip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'TransIP Web Firewall (TransIP)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-TransIP-Backend', '.+')): 12 | return True 13 | 14 | if self.matchHeader(('X-TransIP-Balancer', '.+')): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/uewaf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'UEWaf (UCloud)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'uewaf(/[0-9\.]+)?')): 12 | return True 13 | 14 | if self.matchContent(r'/uewaf_deny_pages/default/img/'): 15 | return True 16 | 17 | if self.matchContent(r'ucloud\.cn'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/urlmaster.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'URLMaster SecurityCheck (iFinity/DotNetNuke)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | return False 18 | 19 | 20 | def check_schema_01(self): 21 | if self.matchHeader(('X-UrlMaster-Debug', '.+')): 22 | return True 23 | 24 | if self.matchHeader(('X-UrlMaster-Ex', '.+')): 25 | return True 26 | 27 | return False 28 | 29 | 30 | def check_schema_02(self): 31 | if not self.matchContent(r"Ur[li]RewriteModule"): 32 | return False 33 | 34 | if not self.matchContent(r'SecurityCheck'): 35 | return False 36 | 37 | return True 38 | -------------------------------------------------------------------------------- /wafw00f/plugins/urlscan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'URLScan (Microsoft)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r"Rejected[-_]By[_-]UrlScan"): 12 | return True 13 | 14 | if self.matchContent(r'A custom filter or module.{0,4}?such as URLScan'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/variti.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Variti (Variti)' 8 | 9 | def is_waf(self): 10 | if self.matchHeader(('Server', r'Variti(?:\/[a-z0-9\.\-]+)?')): 11 | return True 12 | 13 | return False 14 | -------------------------------------------------------------------------------- /wafw00f/plugins/varnish.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Varnish (OWASP)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'Request rejected by xVarnish\-WAF'): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/viettel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Viettel (Cloudrity)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r"Access Denied.{0,10}?Viettel WAF"): 12 | return True 13 | 14 | if self.matchContent(r"cloudrity\.com\.(vn)?/"): 15 | return True 16 | 17 | if self.matchContent(r"Viettel WAF System"): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/virusdie.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'VirusDie (VirusDie LLC)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r"cdn\.virusdie\.ru/splash/firewallstop\.png"): 12 | return True 13 | 14 | if self.matchContent(r'copy.{0,10}?Virusdie\.ru'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/wallarm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Wallarm (Wallarm Inc.)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'nginx[\-_]wallarm')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/watchguard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'WatchGuard (WatchGuard Technologies)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'WatchGuard')): 12 | return True 13 | 14 | if self.matchContent(r"Request denied by WatchGuard Firewall"): 15 | return True 16 | 17 | if self.matchContent(r'WatchGuard Technologies Inc\.'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/webarx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'WebARX (WebARX Security Solutions)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r"WebARX.{0,10}?Web Application Firewall"): 12 | return True 13 | 14 | if self.matchContent(r"www\.webarxsecurity\.com"): 15 | return True 16 | 17 | if self.matchContent(r'/wp\-content/plugins/webarx/includes/'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/webknight.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'WebKnight (AQTRONIX)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | if check_schema_03(self): 18 | return True 19 | 20 | return False 21 | 22 | 23 | def check_schema_01(self): 24 | if not self.matchStatus(999): 25 | return False 26 | 27 | if not self.matchReason('No Hacking'): 28 | return False 29 | 30 | return True 31 | 32 | 33 | def check_schema_02(self): 34 | if not self.matchStatus(404): 35 | return False 36 | 37 | if not self.matchReason('Hack Not Found'): 38 | return False 39 | 40 | return True 41 | 42 | 43 | def check_schema_03(self): 44 | if self.matchContent(r'WebKnight Application Firewall Alert'): 45 | return True 46 | 47 | if self.matchContent(r'What is webknight\?'): 48 | return True 49 | 50 | if self.matchContent(r'AQTRONIX WebKnight is an application firewall'): 51 | return True 52 | 53 | if self.matchContent(r'WebKnight will take over and protect'): 54 | return True 55 | 56 | if self.matchContent(r'aqtronix\.com/WebKnight'): 57 | return True 58 | 59 | if self.matchContent(r'AQTRONIX.{0,10}?WebKnight'): 60 | return True 61 | 62 | return False 63 | -------------------------------------------------------------------------------- /wafw00f/plugins/webland.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'WebLand (WebLand)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'protected by webland')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/webray.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'RayWAF (WebRay Solutions)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'WebRay\-WAF')): 12 | return True 13 | 14 | if self.matchHeader(('DrivedBy', r'RaySrv.RayEng/[0-9\.]+?')): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/webseal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'WebSEAL (IBM)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'WebSEAL')): 12 | return True 13 | 14 | if self.matchContent(r"This is a WebSEAL error message template file"): 15 | return True 16 | 17 | if self.matchContent(r"WebSEAL server received an invalid HTTP request"): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/webtotem.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'WebTotem (WebTotem)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r"The current request was blocked.{0,8}?>WebTotem"): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/west263cdn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'West263 CDN (West263CDN)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-Cache', r'WS?T263CDN')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /wafw00f/plugins/wordfence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Wordfence (Defiant)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'wf[_\-]?WAF')): 12 | return True 13 | 14 | if self.matchContent(r"Generated by Wordfence"): 15 | return True 16 | 17 | if self.matchContent(r'broke one of (the )?Wordfence (advanced )?blocking rules'): 18 | return True 19 | 20 | if self.matchContent(r"/plugins/wordfence"): 21 | return True 22 | 23 | return False 24 | -------------------------------------------------------------------------------- /wafw00f/plugins/wpmudev.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'wpmudev WAF (Incsub)' 8 | 9 | 10 | def is_waf(self): 11 | if check_schema_01(self): 12 | return True 13 | 14 | if check_schema_02(self): 15 | return True 16 | 17 | return False 18 | 19 | 20 | def check_schema_01(self): 21 | if not self.matchContent(r'href="http(s)?.\/\/wpmudev.com\/.{0,15}?'): 22 | return False 23 | 24 | if not self.matchContent(r'Click on the Logs tab, then the WAF Log.'): 25 | return False 26 | 27 | if not self.matchContent(r'Choose your site from the list'): 28 | return False 29 | 30 | if not self.matchStatus(403): 31 | return False 32 | 33 | return True 34 | 35 | 36 | def check_schema_02(self): 37 | if not self.matchContent(r'<h1>Whoops, this request has been blocked!'): 38 | return False 39 | 40 | if not self.matchContent(r'This request has been deemed suspicious'): 41 | return False 42 | 43 | if not self.matchContent(r'possible attack on our servers.'): 44 | return False 45 | 46 | if not self.matchStatus(403): 47 | return False 48 | 49 | return True 50 | -------------------------------------------------------------------------------- /wafw00f/plugins/wts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'WTS-WAF (WTS)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'wts/[0-9\.]+?')): 12 | return True 13 | 14 | if self.matchContent(r"<(title|h\d{1})>WTS\-WAF"): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/wzb360.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = '360WangZhanBao (360 Technologies)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'qianxin\-waf')): 12 | return True 13 | 14 | if self.matchHeader(('WZWS-Ray', r'.+?')): 15 | return True 16 | 17 | if self.matchHeader(('X-Powered-By-360WZB', r'.+?')): 18 | return True 19 | 20 | if self.matchContent(r'wzws\-waf\-cgi/'): 21 | return True 22 | 23 | if self.matchContent(r'wangshan\.360\.cn'): 24 | return True 25 | 26 | if self.matchStatus(493): 27 | return True 28 | 29 | return False 30 | -------------------------------------------------------------------------------- /wafw00f/plugins/xlabssecuritywaf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'XLabs Security WAF (XLabs)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('X-CDN', r'XLabs Security')): 12 | return True 13 | 14 | if self.matchHeader(('Secured', r'^By XLabs Security')): 15 | return True 16 | 17 | if self.matchHeader(('Server', r'XLabs[-_]?.?WAF'), attack=True): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/xuanwudun.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Xuanwudun (Xuanwudun)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r"admin\.dbappwaf\.cn/(index\.php/Admin/ClientMisinform/)?"): 12 | return True 13 | 14 | if self.matchContent(r'class=.(db[\-_]?)?waf(.)?([\-_]?row)?>'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/yundun.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Yundun (Yundun)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'YUNDUN')): 12 | return True 13 | 14 | if self.matchHeader(('X-Cache', 'YUNDUN')): 15 | return True 16 | 17 | if self.matchCookie(r'^yd_cookie='): 18 | return True 19 | 20 | if self.matchContent(r'Blocked by YUNDUN Cloud WAF'): 21 | return True 22 | 23 | if self.matchContent(r'yundun\.com/yd[-_]http[_-]error/'): 24 | return True 25 | 26 | if self.matchContent(r'www\.yundun\.com/(static/js/fingerprint\d{1}?\.js)?'): 27 | return True 28 | 29 | return False 30 | -------------------------------------------------------------------------------- /wafw00f/plugins/yunsuo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Yunsuo (Yunsuo)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie(r'^yunsuo_session='): 12 | return True 13 | 14 | if self.matchContent(r'class=\"yunsuologo\"'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /wafw00f/plugins/yxlink.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'YXLink (YxLink Technologies)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie(r'^yx_ci_session='): 12 | return True 13 | 14 | if self.matchCookie(r'^yx_language='): 15 | return True 16 | 17 | if self.matchHeader(('Server', r'Yxlink([\-_]?WAF)?')): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/zenedge.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'Zenedge (Zenedge)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'ZENEDGE')): 12 | return True 13 | 14 | if self.matchHeader(('X-Zen-Fury', r'.+?')): 15 | return True 16 | 17 | if self.matchContent(r'/__zenedge/'): 18 | return True 19 | 20 | return False 21 | -------------------------------------------------------------------------------- /wafw00f/plugins/zscaler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | NAME = 'ZScaler (Accenture)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'ZScaler')): 12 | return True 13 | 14 | if self.matchContent(r"Access Denied.{0,10}?Accenture Policy"): 15 | return True 16 | 17 | if self.matchContent(r'policies\.accenture\.com'): 18 | return True 19 | 20 | if self.matchContent(r'login\.zscloud\.net/img_logo_new1\.png'): 21 | return True 22 | 23 | if self.matchContent(r'Zscaler to protect you from internet threats'): 24 | return True 25 | 26 | if self.matchContent(r"Internet Security by ZScaler"): 27 | return True 28 | 29 | if self.matchContent(r"Accenture.{0,10}?webfilters indicate that the site likely contains"): 30 | return True 31 | 32 | return False 33 | -------------------------------------------------------------------------------- /wafw00f/wafprio.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | Copyright (C) 2024, WAFW00F Developers. 4 | See the LICENSE file for copying permission. 5 | ''' 6 | 7 | # NOTE: this priority list is used so that each check can be prioritized, 8 | # so that the quick checks are done first and ones that require more 9 | # requests, are done later 10 | 11 | 12 | wafdetectionsprio = [ 13 | '360WangZhanBao (360 Technologies)', 14 | 'ACE XML Gateway (Cisco)', 15 | 'ASP.NET Generic (Microsoft)', 16 | 'ASPA Firewall (ASPA Engineering Co.)', 17 | 'AWS Elastic Load Balancer (Amazon)', 18 | 'AireeCDN (Airee)', 19 | 'Airlock (Phion/Ergon)', 20 | 'Alert Logic (Alert Logic)', 21 | 'AliYunDun (Alibaba Cloud Computing)', 22 | 'AnYu (AnYu Technologies)', 23 | 'Anquanbao (Anquanbao)', 24 | 'AppWall (Radware)', 25 | 'Approach (Approach)', 26 | 'Armor Defense (Armor)', 27 | 'ArvanCloud (ArvanCloud)', 28 | 'Astra (Czar Securities)', 29 | 'Azion Edge Firewall (Azion)', 30 | 'Azure Application Gateway (Microsoft)', 31 | 'Azure Front Door (Microsoft)', 32 | 'BIG-IP AP Manager (F5 Networks)', 33 | 'BIG-IP AppSec Manager (F5 Networks)', 34 | 'BIG-IP Local Traffic Manager (F5 Networks)', 35 | 'Barikode (Ethic Ninja)', 36 | 'Barracuda (Barracuda Networks)', 37 | 'Bekchy (Faydata Technologies Inc.)', 38 | 'Beluga CDN (Beluga)', 39 | 'BinarySec (BinarySec)', 40 | 'BitNinja (BitNinja)', 41 | 'BlockDoS (BlockDoS)', 42 | 'Bluedon (Bluedon IST)', 43 | 'BulletProof Security Pro (AITpro Security)', 44 | 'CacheFly CDN (CacheFly)', 45 | 'CacheWall (Varnish)', 46 | 'CdnNS Application Gateway (CdnNs/WdidcNet)', 47 | 'ChinaCache Load Balancer (ChinaCache)', 48 | 'Chuang Yu Shield (Yunaq)', 49 | 'Cloud Protector (Rohde & Schwarz CyberSecurity)', 50 | 'Cloudbric (Penta Security)', 51 | 'Cloudflare (Cloudflare Inc.)', 52 | 'Cloudfloor (Cloudfloor DNS)', 53 | 'Cloudfront (Amazon)', 54 | 'Comodo cWatch (Comodo CyberSecurity)', 55 | 'CrawlProtect (Jean-Denis Brun)', 56 | 'DDoS-GUARD (DDOS-GUARD CORP.)', 57 | 'DOSarrest (DOSarrest Internet Security)', 58 | 'DataPower (IBM)', 59 | 'DenyALL (Rohde & Schwarz CyberSecurity)', 60 | 'Distil (Distil Networks)', 61 | 'DotDefender (Applicure Technologies)', 62 | 'DynamicWeb Injection Check (DynamicWeb)', 63 | 'Edgecast (Verizon Digital Media)', 64 | 'Eisoo Cloud Firewall (Eisoo)', 65 | 'Envoy (EnvoyProxy)', 66 | 'Expression Engine (EllisLab)', 67 | 'Fastly (Fastly CDN)', 68 | 'FirePass (F5 Networks)', 69 | 'FortiGate (Fortinet)', 70 | 'FortiGuard (Fortinet)', 71 | 'FortiWeb (Fortinet)', 72 | 'GoDaddy Website Protection (GoDaddy)', 73 | 'Google Cloud App Armor (Google Cloud)', 74 | 'Greywizard (Grey Wizard)', 75 | 'Huawei Cloud Firewall (Huawei)', 76 | 'HyperGuard (Art of Defense)', 77 | 'ISA Server (Microsoft)', 78 | 'Imunify360 (CloudLinux)', 79 | 'Incapsula (Imperva Inc.)', 80 | 'IndusGuard (Indusface)', 81 | 'Instart DX (Instart Logic)', 82 | 'Janusec Application Gateway (Janusec)', 83 | 'Jiasule (Jiasule)', 84 | 'KS-WAF (KnownSec)', 85 | 'Kemp LoadMaster (Progress Software)', 86 | 'KeyCDN (KeyCDN)', 87 | 'Kona SiteDefender (Akamai)', 88 | 'LimeLight CDN (LimeLight)', 89 | 'LiteSpeed (LiteSpeed Technologies)', 90 | 'Malcare (Inactiv)', 91 | 'MaxCDN (MaxCDN)', 92 | 'Mission Control Shield (Mission Control)', 93 | 'ModSecurity (SpiderLabs)', 94 | 'NAXSI (NBS Systems)', 95 | 'NSFocus (NSFocus Global Inc.)', 96 | 'Nemesida (PentestIt)', 97 | 'NetContinuum (Barracuda Networks)', 98 | 'NetScaler AppFirewall (Citrix Systems)', 99 | 'NevisProxy (AdNovum)', 100 | 'Newdefend (NewDefend)', 101 | 'NexusGuard Firewall (NexusGuard)', 102 | 'NinjaFirewall (NinTechNet)', 103 | 'NullDDoS Protection (NullDDoS)', 104 | 'OnMessage Shield (BlackBaud)', 105 | 'Open-Resty Lua Nginx (FLOSS)', 106 | 'Oracle Cloud (Oracle)', 107 | 'PT Application Firewall (Positive Technologies)', 108 | 'Palo Alto Next Gen Firewall (Palo Alto Networks)', 109 | 'PentaWAF (Global Network Services)', 110 | 'PerimeterX (PerimeterX)', 111 | 'PowerCDN (PowerCDN)', 112 | 'Profense (ArmorLogic)', 113 | 'Puhui (Puhui)', 114 | 'Qcloud (Tencent Cloud)', 115 | 'Qiniu (Qiniu CDN)', 116 | 'Qrator (Qrator)', 117 | 'RSFirewall (RSJoomla!)', 118 | 'RayWAF (WebRay Solutions)', 119 | 'Reblaze (Reblaze)', 120 | 'RequestValidationMode (Microsoft)', 121 | 'SEnginx (Neusoft)', 122 | 'Sabre Firewall (Sabre)', 123 | 'Safe3 Web Firewall (Safe3)', 124 | 'Safedog (SafeDog)', 125 | 'Safeline (Chaitin Tech.)', 126 | 'SecKing (SecKing)', 127 | 'SecuPress WP Security (SecuPress)', 128 | 'Secure Entry (United Security Providers)', 129 | 'SecureSphere (Imperva Inc.)', 130 | 'ServerDefender VP (Port80 Software)', 131 | 'Shadow Daemon (Zecure)', 132 | 'Shield Security (One Dollar Plugin)', 133 | 'SiteGround (SiteGround)', 134 | 'SiteGuard (Sakura Inc.)', 135 | 'Sitelock (TrueShield)', 136 | 'SonicWall (Dell)', 137 | 'Squarespace (Squarespace)', 138 | 'SquidProxy IDS (SquidProxy)', 139 | 'StackPath (StackPath)', 140 | 'Sucuri CloudProxy (Sucuri Inc.)', 141 | 'Tencent Cloud Firewall (Tencent Technologies)', 142 | 'Teros (Citrix Systems)', 143 | 'Trafficshield (F5 Networks)', 144 | 'TransIP Web Firewall (TransIP)', 145 | 'UEWaf (UCloud)', 146 | 'URLMaster SecurityCheck (iFinity/DotNetNuke)', 147 | 'URLScan (Microsoft)', 148 | 'UTM Web Protection (Sophos)', 149 | 'Variti (Variti)', 150 | 'Varnish (OWASP)', 151 | 'Viettel (Cloudrity)', 152 | 'VirusDie (VirusDie LLC)', 153 | 'WP Cerber Security (Cerber Tech)', 154 | 'WTS-WAF (WTS)', 155 | 'Wallarm (Wallarm Inc.)', 156 | 'WatchGuard (WatchGuard Technologies)', 157 | 'WebARX (WebARX Security Solutions)', 158 | 'WebKnight (AQTRONIX)', 159 | 'WebLand (WebLand)', 160 | 'WebSEAL (IBM)', 161 | 'WebTotem (WebTotem)', 162 | 'West263 CDN (West263CDN)', 163 | 'Wordfence (Defiant)', 164 | 'XLabs Security WAF (XLabs)', 165 | 'Xuanwudun (Xuanwudun)', 166 | 'YXLink (YxLink Technologies)', 167 | 'Yundun (Yundun)', 168 | 'Yunjiasu (Baidu Cloud Computing)', 169 | 'Yunsuo (Yunsuo)', 170 | 'ZScaler (Accenture)', 171 | 'Zenedge (Zenedge)', 172 | 'aeSecure (aeSecure)', 173 | 'eEye SecureIIS (BeyondTrust)', 174 | 'pkSecurity IDS (pkSec)', 175 | 'wpmudev WAF (Incsub)' 176 | ] --------------------------------------------------------------------------------