├── db ├── config.yaml ├── dictionary │ ├── dicc.txt │ ├── subnames.txt │ └── subwords.txt ├── finger.json ├── plugins │ └── waf │ │ ├── __init__.py │ │ ├── aesecure.py │ │ ├── airee.py │ │ ├── airlock.py │ │ ├── alertlogic.py │ │ ├── aliyundun.py │ │ ├── anquanbao.py │ │ ├── anyu.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 │ │ ├── expressionengine.py │ │ ├── f5_big_ip.py │ │ ├── f5bigipapm.py │ │ ├── f5bigipasm.py │ │ ├── f5bigipltm.py │ │ ├── f5firepass.py │ │ ├── f5trafficshield.py │ │ ├── fastly.py │ │ ├── fortiweb.py │ │ ├── frontdoor.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 │ │ ├── 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 │ │ ├── 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 ├── qqwry.dat ├── report.html └── subdomain │ ├── certificates │ ├── certspotter.yaml │ └── crtsh.yaml │ ├── datasets │ ├── chinaz.yaml │ ├── hackertarget.yaml │ ├── ip138.yaml │ ├── qianxian.yaml │ ├── rapiddns.yaml │ └── riddler.yaml │ └── intelligence │ ├── alienvault_dns.yaml │ └── alienvault_url.yaml ├── jws-cli.py ├── lib ├── __init__.py ├── core │ ├── __init__.py │ ├── check.py │ ├── controller.py │ ├── dingding.py │ ├── log.py │ ├── report.py │ └── settings.py ├── modules │ ├── __init__.py │ ├── auto │ │ ├── __init__.py │ │ ├── auto_scan.py │ │ └── utils.py │ ├── cdn │ │ ├── __init__.py │ │ ├── cdn_scan.py │ │ ├── dns_resolver.py │ │ └── qqwry │ │ │ ├── __init__.py │ │ │ ├── cz88update.py │ │ │ ├── py.typed │ │ │ └── qqwry.py │ ├── cidr │ │ ├── __init__.py │ │ ├── cidr_scan.py │ │ ├── cidr_system.py │ │ └── thirdparty.py │ ├── company │ │ ├── company_scan.py │ │ └── icp.py │ ├── finger │ │ ├── Wappalyzer │ │ │ ├── Wappalyzer.py │ │ │ ├── __init__.py │ │ │ └── data │ │ │ │ └── technologies.json │ │ ├── __init__.py │ │ └── finger_scan.py │ ├── poc │ │ ├── poc_scan.py │ │ └── thirdparty.py │ ├── port │ │ ├── __init__.py │ │ ├── async_scan.py │ │ ├── banner_scan.py │ │ ├── check_overflow.py │ │ ├── host_scan.py │ │ ├── port_scan.py │ │ ├── thirdparty.py │ │ └── thread_scan.py │ ├── search │ │ ├── __init__.py │ │ ├── api_base.py │ │ ├── api_binaryedge.py │ │ ├── api_censys.py │ │ ├── api_dnsdumpster.py │ │ ├── api_fofa.py │ │ ├── api_fullhunt.py │ │ ├── api_hunter.py │ │ ├── api_quake.py │ │ ├── api_robtex.py │ │ ├── api_securitytrails.py │ │ ├── api_virustotal.py │ │ ├── api_zero.py │ │ └── api_zoomeye.py │ ├── sub │ │ ├── __init__.py │ │ ├── bruteforc │ │ │ ├── __init__.py │ │ │ ├── brute.py │ │ │ ├── dnsgen.py │ │ │ └── thirdparty.py │ │ ├── custom.py │ │ ├── search.py │ │ ├── sub_scan.py │ │ ├── utils.py │ │ └── vulnerability │ │ │ ├── __init__.py │ │ │ └── dns_zone_transfer.py │ └── waf │ │ └── waf_scan.py └── utils │ ├── __init__.py │ ├── encrypt.py │ ├── file.py │ ├── getfiles.py │ ├── mail.py │ ├── thread.py │ └── tools.py ├── readme.md ├── requirements.txt └── thirdparty ├── afrog ├── ksubdomain └── nimscan /db/config.yaml: -------------------------------------------------------------------------------- 1 | # 开启/关闭 调试模式 2 | debug_mode: False 3 | 4 | # 开启/关闭 数据表格展示 5 | show_table: True 6 | 7 | # 配置程序需要调用的api接口信息 # 8 | api_key: 9 | # 零零信安 https://0.zone/ 10 | zero_key: "" 11 | zero_size: 200 12 | 13 | # quake https://quake.360.net/quake/#/index 14 | quake_key: "" 15 | quake_size: 200 16 | 17 | # zoomeye https://www.zoomeye.org/ 18 | zoomeye_mail: "" # zoomeye账号 19 | zoomeye_pass: "" # zoomeye密码 20 | zoomeye_size: 200 # 最大检索量 21 | 22 | # hunter https://hunter.qianxin.com/ 23 | hunter_key: "" 24 | hunter_size: 200 # 最大检索量 25 | 26 | # fofa https://fofa.info/ 27 | fofa_email: "" 28 | fofa_key: "" 29 | fofa_size: 200 # 最大检索量 30 | 31 | # securitytrails https://securitytrails.com/ 32 | securitytrails_key: "" 33 | 34 | # fullhunt https://fullhunt.io/search 35 | fullhunt_key: "" 36 | 37 | # binaryedge https://binaryedge.io/ 38 | binaryedge_key: "" 39 | 40 | # censys https://search.censys.io/ 41 | censys_id: "" 42 | censys_secret: "" 43 | 44 | 45 | # 自动化扫描配置 # 46 | # 默认情况下,程序自动进行子域名收集和存活资产探测任务。你也可以根据自己的需求,自由搭配需要开启的扫描模块。 47 | auto_setting: 48 | port_scan: True # 开启/关闭 主机端口扫描。 49 | cidr_scan: True # 开启/关闭 C段资产扫描。 50 | poc_scan: True # 开启/关闭 POC漏洞扫描。 51 | 52 | # 开启/关闭 智能模式。 53 | # 智能模式下,会减少开销。仅对没有waf的url进行POC扫描。 54 | smart_mode: True 55 | 56 | # 支持通过定制化黑名单列表排除没有意义的C段资产,仅当 cidr_scan = True 时有效,列表中的值对应IP解析后的地址信息 57 | filter_blacklist: [ 58 | '微软', '阿里云', '阿里云BGP节点', '阿里云BGP服务器', '阿里巴巴', 'Microsoft', 'CDN', 'Azure', '华为', '华为云', 59 | '腾讯云', '网宿', 'Amazon', '运营商','世纪互联BGP数据中心', '内部网', '局域网', '对方和您在同一内部网', '亚马逊', '127.0.0.1' 60 | ] 61 | 62 | # 开启/关闭 生成扫描报告 63 | generate_report: True 64 | 65 | # 邮箱信息配置 66 | smtp_server: smtp.163.com # smtp 邮箱服务器 67 | smtp_port: 465 # smtp 端口号 68 | send_email: "" # 发件人邮箱账号 69 | send_pass: "" # 发件人邮箱授权码 70 | rec_email: "" # 收件人邮箱, 如果有多个收件人需要使用英文逗号隔开 71 | 72 | # dingding 配置 73 | dingding_webhook: '' 74 | dingding_secrets: '' 75 | 76 | 77 | # 子域名扫描配置 # 78 | # 默认情况下,程序自动进行被动子域名信息收集,支持使用域名置换技术生成fuzz字典,支持额外调用ksubdomain来完成域名遍历任务。 79 | sub_scan: 80 | # 爆破模式参数。 81 | brute_engine: "ksubdomain" # 可选参数:system 和 ksubdomain。 82 | brute_fuzzy: False # 开启/关闭 域名置换技术。 83 | 84 | 85 | # 端口扫描配置 # 86 | # 默认情况下,程序会对主机进行存活探测,并对存活的端口进行指纹识别。支持调用nimscan完成端口扫描任务,支持自定义要扫描的端口范围。 87 | port_scan: 88 | engine: "nimscan" # 可选参数:system 和 nimscan。 89 | banner_status: True # 开启/关闭 指纹识别。 90 | port_range: '21,22,23,80-99,135,139,442-445,666,800,801,808,880,888,889,1000-2379,3000-10010,11115,12018,12443,14000,16080,18000-18098,19001,19080,20000,20720,21000,21501,21502,28018,20880,27017' 91 | 92 | 93 | # C段扫描配置 # 94 | # 支持统计目标C段中,资产IP出现的次数;支持使用 occurrence_limit 参数跳过不符合条件的C段。 95 | cidr_scan: 96 | engine: "system" # 可选参数:system 和 fofa 97 | occurrence_limit: 2 # 如果相同C段统计IP出现次数,次数>=2才扫描 98 | 99 | 100 | # POC扫描配置 # 101 | # 支持调用afrog完成扫描任务。 102 | poc_scan: 103 | engine: "afrog" # 可选参数:afrog 104 | -------------------------------------------------------------------------------- /db/dictionary/subwords.txt: -------------------------------------------------------------------------------- 1 | dev 2 | test 3 | stg 4 | svc 5 | staging 6 | stage 7 | prod 8 | secure 9 | trial 10 | admin 11 | acc 12 | account 13 | accounting 14 | active 15 | adm 16 | administrator 17 | administrators 18 | admins 19 | alpha 20 | analytics 21 | apac 22 | apache 23 | api 24 | apidocs 25 | apiserver 26 | app 27 | apps 28 | application 29 | applications 30 | assets 31 | asana 32 | auth 33 | authenticate 34 | authentication 35 | authorization 36 | aws 37 | azure 38 | backend 39 | beta 40 | billing 41 | bitbucket 42 | brand 43 | bucket 44 | cdn 45 | cert 46 | cgi 47 | chd 48 | chef 49 | ci 50 | client 51 | cloud 52 | cloudapp 53 | cloudfront 54 | cms 55 | confluence 56 | container 57 | control 58 | controller 59 | ctl 60 | customer 61 | cvs 62 | data 63 | dashboard 64 | demo 65 | developer 66 | development 67 | devops 68 | develop 69 | devs 70 | disabled 71 | docker 72 | docs 73 | docsapi 74 | document 75 | documents 76 | documentation 77 | edge 78 | elastic 79 | elasticbeanstalk 80 | ebs 81 | email 82 | emea 83 | engine 84 | engineering 85 | europe 86 | europewest 87 | events 88 | ext 89 | file 90 | firewall 91 | frontpage 92 | fw 93 | gateway 94 | gh 95 | get 96 | getter 97 | gist 98 | git 99 | github 100 | gitlab 101 | global 102 | gw 103 | help 104 | history 105 | hw 106 | hwcdn 107 | iad 108 | ids 109 | internal 110 | ingress 111 | internals 112 | jenkins 113 | k8s 114 | k8s-dev 115 | k8s-prd 116 | k8s-prod 117 | kube 118 | kubectl 119 | kubernetes 120 | lab 121 | latin 122 | legacy 123 | loadbalancer 124 | login 125 | machine 126 | manage 127 | management 128 | mgmt 129 | mail 130 | marketing 131 | market 132 | metrics 133 | metric 134 | merchant 135 | mirror 136 | mobile 137 | mobileclient 138 | nautilus 139 | net 140 | nginx 141 | node 142 | northamerica 143 | oid 144 | old 145 | ops 146 | org 147 | origin 148 | page 149 | panel 150 | partner 151 | pass 152 | pay 153 | payment 154 | paywall 155 | php 156 | portal 157 | preview 158 | priv 159 | private 160 | prd 161 | production 162 | productions 163 | profile 164 | profiles 165 | promo 166 | proxy 167 | raw 168 | redir 169 | redirect 170 | redirector 171 | region 172 | repo 173 | repository 174 | reset 175 | restricted 176 | rpc 177 | s3 178 | sandbox 179 | scm 180 | search 181 | security 182 | server 183 | service 184 | signed 185 | skins 186 | ssl 187 | staff 188 | static 189 | stats 190 | support 191 | swagger 192 | swag 193 | system 194 | team 195 | testing 196 | tester 197 | traffic 198 | tomcat 199 | toolbar 200 | train 201 | training 202 | upload 203 | uploads 204 | v1 205 | v2 206 | v3 207 | vpn 208 | w3 209 | web 210 | webapp 211 | -------------------------------------------------------------------------------- /db/plugins/waf/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jammny/jws-cli/b454bd0cdae2fae7a086485375dba8b419d6ad05/db/plugins/waf/__init__.py -------------------------------------------------------------------------------- /db/plugins/waf/aesecure.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/airee.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/airlock.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/alertlogic.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/aliyundun.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 = 'AliYunDun (Alibaba Cloud Computing)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'阿里云 Web应用防火墙'): 12 | return True 13 | 14 | if self.matchContent(r'很抱歉,由于您访问的URL有可能对网站造成安全威胁,您的访问被阻断。'): 15 | return True 16 | 17 | if self.matchContent(r'error(s)?\.aliyun(dun)?\.(com|net)?'): 18 | return True 19 | 20 | if self.matchContent(r'alicdn\.com\/sd\-base\/static\/\d{1,2}\.\d{1,2}\.\d{1,2}\/image\/405\.png'): 21 | return True 22 | 23 | if self.matchContent(r'Sorry, your request has been blocked as it may cause potential threats to the server\'s security.'): 24 | return True 25 | 26 | return False 27 | -------------------------------------------------------------------------------- /db/plugins/waf/anquanbao.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/anyu.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/approach.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/armor.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/arvancloud.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 = 'ArvanCloud (ArvanCloud)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'ArvanCloud')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /db/plugins/waf/aspa.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/aspnetgen.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/astra.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/awswaf.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 = '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 | return False 24 | -------------------------------------------------------------------------------- /db/plugins/waf/azion.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 = 'AzionCDN (AzionCDN)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'Azion([-_]CDN)?')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /db/plugins/waf/baidu.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 = 'Yunjiasu (Baidu Cloud Computing)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'Yunjiasu(.+)?')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /db/plugins/waf/barikode.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/barracuda.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/bekchy.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/beluga.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/binarysec.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/bitninja.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/blockdos.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/bluedon.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/bulletproof.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/cachefly.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/cachewall.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/cdnns.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/cerber.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/chinacache.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/chuangyu.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/ciscoacexml.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/cloudbric.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/cloudflare.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/cloudfloordns.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/cloudfront.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/cloudprotector.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/comodo.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/crawlprotect.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/ddosguard.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/denyall.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/distil.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/dosarrest.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/dotdefender.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/dynamicweb.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/edgecast.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/eisoo.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/expressionengine.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/f5_big_ip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 6 | """ 7 | NAME = 'F5 BIG-IP (F5 BIG-IP)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent('<title>操作可能存在安全隐患'): 12 | return True 13 | 14 | if self.matchCookie(r'^BIGipServerDS', attack=True): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /db/plugins/waf/f5bigipapm.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/f5bigipasm.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/f5bigipltm.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/f5firepass.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/f5trafficshield.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/fastly.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/fortiweb.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/frontdoor.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/godaddy.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/greywizard.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/huaweicloud.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/hyperguard.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 = 'HyperGuard (Art of Defense)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie('^WODSESSION='): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /db/plugins/waf/ibmdatapower.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/imunify360.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/incapsula.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/indusguard.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/instartdx.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/isaserver.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/janusec.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/jiasule.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/keycdn.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 = 'KeyCDN (KeyCDN)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', 'KeyCDN')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /db/plugins/waf/knownsec.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/kona.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | #!/usr/bin/env python 5 | ''' 6 | Copyright (C) 2022, 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 | -------------------------------------------------------------------------------- /db/plugins/waf/limelight.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/litespeed.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/malcare.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/maxcdn.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/missioncontrol.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/modsecurity.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/naxsi.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/nemesida.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/netcontinuum.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/netscaler.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/nevisproxy.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/newdefend.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/nexusguard.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/ninja.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/nsfocus.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/nullddos.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/onmessage.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/openresty.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/oraclecloud.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/paloalto.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/pentawaf.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/perimeterx.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/pksec.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/powercdn.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/profense.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/ptaf.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/puhui.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/qcloud.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 = 'Qcloud (Tencent Cloud)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'腾讯云Web应用防火墙'): 12 | return True 13 | 14 | if self.matchContent(r'https://imgcache.qq.com/qcloud/security/static/imgs/attackIntercept.svg') \ 15 | and self.matchStatus(403): 16 | return True 17 | 18 | return False 19 | -------------------------------------------------------------------------------- /db/plugins/waf/qiniu.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/qrator.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 = 'Qrator (Qrator)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'QRATOR')): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /db/plugins/waf/radware.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/reblaze.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/rsfirewall.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/rvmode.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/sabre.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/safe3.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/safedog.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/safeline.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/secking.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/secupress.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/secureentry.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/secureiis.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/securesphere.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 = 'SecureSphere (Imperva Inc.)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r'<(title|h2)>Error'): 12 | return True 13 | 14 | if self.matchContent(r'The incident ID is'): 15 | return True 16 | 17 | if self.matchContent(r"This page can't be displayed"): 18 | return True 19 | 20 | if self.matchContent(r'Contact support for additional information'): 21 | return True 22 | 23 | return False 24 | -------------------------------------------------------------------------------- /db/plugins/waf/senginx.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/serverdefender.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/shadowd.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 = 'Shadow Daemon (Zecure)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchContent(r"<h\d{1}>\d{3}.forbidden<.h\d{1}>"): 12 | return True 13 | 14 | if self.matchContent(r"request forbidden by administrative rules"): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /db/plugins/waf/shieldon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 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 | -------------------------------------------------------------------------------- /db/plugins/waf/shieldsecurity.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/siteground.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/siteguard.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/sitelock.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/sonicwall.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/sophos.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/squarespace.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/squidproxy.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 = 'SquidProxy IDS (SquidProxy)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchHeader(('Server', r'squid(/[0-9\.]+)?')): 12 | return True 13 | 14 | if self.matchContent(r'Access control configuration prevents your request'): 15 | return True 16 | 17 | return False 18 | -------------------------------------------------------------------------------- /db/plugins/waf/stackpath.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/sucuri.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/tencent.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/teros.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 = 'Teros (Citrix Systems)' 8 | 9 | 10 | def is_waf(self): 11 | if self.matchCookie(r'^st8id='): 12 | return True 13 | 14 | return False 15 | -------------------------------------------------------------------------------- /db/plugins/waf/transip.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/uewaf.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/urlmaster.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/urlscan.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/varnish.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/viettel.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/virusdie.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/wallarm.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/watchguard.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/webarx.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/webknight.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/webland.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/webray.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/webseal.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/webtotem.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/west263cdn.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/wordfence.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/wpmudev.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/wts.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/wzb360.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/xlabssecuritywaf.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/xuanwudun.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/yundun.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/yunsuo.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/yxlink.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/zenedge.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 = '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 | -------------------------------------------------------------------------------- /db/plugins/waf/zscaler.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 = '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 | -------------------------------------------------------------------------------- /db/qqwry.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jammny/jws-cli/b454bd0cdae2fae7a086485375dba8b419d6ad05/db/qqwry.dat -------------------------------------------------------------------------------- /db/subdomain/certificates/certspotter.yaml: -------------------------------------------------------------------------------- 1 | id: certspotter 2 | # 通过certspotter,收集包含相同证书的域名信息 3 | 4 | rule: 5 | while: False 6 | 7 | request: 8 | header: 9 | User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0 10 | method: get 11 | timeout: 10 12 | url: https://api.certspotter.com/v1/issuances?include_subdomains=true&expand=dns_names&domain={domain} 13 | 14 | response: 15 | code: 200 16 | 17 | -------------------------------------------------------------------------------- /db/subdomain/certificates/crtsh.yaml: -------------------------------------------------------------------------------- 1 | id: crt.sh 2 | # 通过crt.sh查询dns解析记录 3 | 4 | rule: 5 | while: False 6 | 7 | request: 8 | header: 9 | User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0 10 | method: get 11 | timeout: 10 12 | url: https://crt.sh/?q={domain}&output=json 13 | 14 | response: 15 | code: 200 16 | -------------------------------------------------------------------------------- /db/subdomain/datasets/chinaz.yaml: -------------------------------------------------------------------------------- 1 | id: chinaz 2 | # https://alexa.chinaz.com 3 | 4 | type: fuzzy 5 | 6 | rule: 7 | while: False 8 | 9 | request: 10 | header: 11 | User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0 12 | method: get 13 | timeout: 10 14 | url: https://alexa.chinaz.com/{domain} 15 | 16 | response: 17 | code: 200 18 | 19 | -------------------------------------------------------------------------------- /db/subdomain/datasets/hackertarget.yaml: -------------------------------------------------------------------------------- 1 | id: hackertarget 2 | # https://api.hackertarget.com/hostsearch/ 3 | # 国内网络应该访问不上 4 | 5 | type: fuzzy 6 | 7 | rule: 8 | # 循环配置 9 | while: False 10 | 11 | # 请求配置 12 | request: 13 | url: "https://api.hackertarget.com/hostsearch/?q={domain}" 14 | method: get 15 | header: 16 | User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 17 | timeout: 10 18 | 19 | # 解析响应 20 | response: 21 | code: 200 22 | -------------------------------------------------------------------------------- /db/subdomain/datasets/ip138.yaml: -------------------------------------------------------------------------------- 1 | id: ip138 2 | # https://site.ip138.com/ 3 | 4 | type: fuzzy 5 | 6 | rule: 7 | while: False 8 | request: 9 | header: 10 | User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0 11 | method: get 12 | timeout: 10 13 | url: https://site.ip138.com/{domain}/domain.htm 14 | 15 | response: 16 | code: 200 17 | 18 | -------------------------------------------------------------------------------- /db/subdomain/datasets/qianxian.yaml: -------------------------------------------------------------------------------- 1 | id: qianxun 2 | # https://www.dnsscan.cn/dns.html 3 | 4 | rule: 5 | while: True 6 | 7 | start_page: 1 8 | add_num: 1 9 | 10 | request: 11 | url: "https://www.dnsscan.cn/dns.html?keywords={domain}&page={page}" 12 | method: post 13 | header: 14 | User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 15 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 16 | Content-Type: application/x-www-form-urlencoded 17 | data: "{'ecmsfrom': '127.0.0.1', 'show': '未知', 'num': '', 'classid': '', 'keywords': '{domain}'}" 18 | # 响应很慢很慢 19 | timeout: 30 20 | 21 | response: 22 | code: 200 23 | -------------------------------------------------------------------------------- /db/subdomain/datasets/rapiddns.yaml: -------------------------------------------------------------------------------- 1 | id: rapiddns 2 | # https://rapiddns.io/subdomain 3 | 4 | type: fuzzy 5 | 6 | rule: 7 | while: False 8 | 9 | request: 10 | header: 11 | User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0 12 | method: get 13 | timeout: 10 14 | url: https://rapiddns.io/subdomain/{domain}?full=1 15 | 16 | response: 17 | code: 200 18 | -------------------------------------------------------------------------------- /db/subdomain/datasets/riddler.yaml: -------------------------------------------------------------------------------- 1 | id: riddler 2 | # https://riddler.io/search 3 | 4 | rule: 5 | while: False 6 | 7 | request: 8 | url: "https://riddler.io/search?q=pld:{domain}" 9 | method: get 10 | header: 11 | User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 12 | sec-ch-ua: '"Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"' 13 | sec-ch-ua-mobile: ?0 14 | sec-ch-ua-platform: '"Linux"' 15 | timeout: 10 16 | 17 | response: 18 | code: 200 19 | 20 | -------------------------------------------------------------------------------- /db/subdomain/intelligence/alienvault_dns.yaml: -------------------------------------------------------------------------------- 1 | id: alienvault 2 | 3 | rule: 4 | while: False 5 | 6 | request: 7 | header: 8 | User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0 9 | method: get 10 | timeout: 10 11 | url: https://otx.alienvault.com/api/v1/indicators/domain/{domain}/passive_dns 12 | 13 | 14 | response: 15 | code: 200 -------------------------------------------------------------------------------- /db/subdomain/intelligence/alienvault_url.yaml: -------------------------------------------------------------------------------- 1 | id: alienvault_url 2 | 3 | rule: 4 | while: False 5 | 6 | request: 7 | header: 8 | User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0 9 | method: get 10 | timeout: 10 11 | url: https://otx.alienvault.com/api/v1/indicators/domain/{domain}/url_list?limit=500&page=1 12 | 13 | response: 14 | code: 200 15 | -------------------------------------------------------------------------------- /jws-cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 程序入口。 6 | """ 7 | from typer import Typer, Option 8 | 9 | from lib.core.log import console 10 | from lib.core.settings import BANNER 11 | from lib.core.check import CheckAll, args_check 12 | from lib.core.controller import Router 13 | from lib.modules.company.company_scan import CompanyScan 14 | 15 | app = Typer() 16 | 17 | 18 | @app.command() 19 | def main( 20 | target: str = Option(None, "--target", "-t", help="目标根域名/URL链接"), 21 | query: str = Option(None, "--query", "-q", help="空间搜索引擎语法"), 22 | file: str = Option(None, "--file", "-f", help="批量扫描, 多个目标之间需要换行分隔"), 23 | company: str = Option(None, "--company", "-c", help='企业全称:python jws-cli.py -c "企业全称" --auto'), 24 | 25 | auto: bool = Option(False, "--auto", help="自动化扫描: python jws-cli.py -t example.com --auto"), 26 | finger: bool = Option(False, "--finger", 27 | help="WEB指纹识别: python jws-cli.py -t https://example.com --finger"), 28 | sub: bool = Option(False, "--sub", 29 | help="子域名收集: python jws-cli.py -t example.com --sub [可选:--finger]"), 30 | port: bool = Option(False, "--port", 31 | help="端口扫描: python jws-cli.py -t 192.168.2.1 --port [可选:--finger]"), 32 | cidr: bool = Option(False, "--cidr", 33 | help="C端扫描: python jws-cli.py -t 192.168.2.1 --cidr [可选:--finger]"), 34 | poc: bool = Option(False, "--poc", 35 | help="POC扫描: python jws-cli.py -t https://example.com --poc"), 36 | fofa: bool = Option(False, "--fofa", 37 | help='FOFA搜集: python jws-cli.py -q "fofa语法" --fofa [可选:--poc]'), 38 | ) -> None: 39 | console.print(BANNER) # 输出Banner图案 40 | targets_list: list = args_check(target, file, query, company) # 返回需要扫描的目标列表 41 | check = CheckAll() 42 | check.run() # 程序兼容性检测 43 | 44 | router = Router(targets_list) 45 | if query and fofa: 46 | Router.args_fofa(query, poc) # fofa接口调用 47 | elif company and auto: 48 | domain_list = CompanyScan().run(company) 49 | if domain_list: 50 | r = Router(domain_list) 51 | r.args_auto() # 自动化扫描 52 | elif auto: 53 | router.args_auto() # 自动化扫描 54 | elif sub: 55 | router.args_sub(finger) # 域名收集 56 | elif port: 57 | router.args_port(finger) # 端口扫描 58 | elif cidr: 59 | router.args_cidr(finger) # C段扫描 60 | elif finger: 61 | router.args_finger() # 指纹识别 62 | elif poc: 63 | router.args_poc() # POC扫描 64 | 65 | 66 | if __name__ == "__main__": 67 | app() 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding : utf-8-*- 3 | # coding:unicode_escape 4 | """ 5 | 作者:jammny 6 | 文件描述: 7 | """ 8 | -------------------------------------------------------------------------------- /lib/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jammny/jws-cli/b454bd0cdae2fae7a086485375dba8b419d6ad05/lib/core/__init__.py -------------------------------------------------------------------------------- /lib/core/check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:程序启动前的兼容性检查。 6 | """ 7 | from platform import python_version 8 | from pathlib import Path 9 | 10 | from httpx import Client 11 | 12 | from lib.core.log import logger 13 | from lib.core.settings import REPORTS, VERSION, TMP, THIRDPARTY_APP 14 | 15 | __all__ = ['CheckAll', 'args_check'] 16 | 17 | 18 | def args_check(target, file, query, company) -> list: 19 | """ 20 | 21 | :param target: 目标域名/链接 22 | :param file: 文件路径 23 | :param query: 空间搜索引擎查询语法 24 | :param company: 企业名称 25 | :return: 需要扫描的目标列表 26 | """ 27 | def fuc(s): 28 | # 去掉多余的 \n 空格 / 29 | return s.rstrip("\n").replace(" ", "").rstrip("/") 30 | 31 | input_list: list = [target, file, query, company] 32 | are_all_none = all(item is None for item in input_list) 33 | 34 | # 确保输入必要参数 # 35 | if are_all_none: 36 | logger.info('[y]You need to provide the args like -t/-f/-q/-c , enter "--help" for help!') 37 | raise exit(0) 38 | 39 | # 确保可控参数唯一 # 40 | if input_list.count(None) != 3: 41 | logger.info('[y]The input parameter is incorrect, enter "--help" for help!') 42 | 43 | # 从文件读取目标 # 44 | if file: 45 | with open(file, mode='r', encoding='utf-8') as f: 46 | tmp: list = f.readlines() 47 | target_list: list = [fuc(i) for i in tmp if fuc(i)] 48 | if not target_list: 49 | logger.error('[red]The file is null!') 50 | raise exit(0) 51 | elif company: 52 | target_list: list = [company.rstrip("/")] 53 | elif query: 54 | target_list: list = [query.rstrip("/")] 55 | else: 56 | target_list: list = [target.rstrip("/")] 57 | 58 | return target_list 59 | 60 | 61 | class CheckAll(object): 62 | def __int__(self, target: str, file: str, query: str, company: str): 63 | self.target: str = target # 目标域名 64 | self.file: str = file # 本地文件 65 | self.query: str = query # 查询语句 66 | self.company: str = company # 企业名称 67 | 68 | @staticmethod 69 | def py_version_check() -> None: 70 | """py版本兼容性检测 71 | 72 | :return: None 73 | """ 74 | py_version: str = python_version() 75 | a: list = py_version.split('.') 76 | if int(a[0]) < 3 or int(a[1]) < 8: 77 | logger.error(f"[red]The current version ({py_version}) is not compatible, maybe need at least >= 3.11)") 78 | raise exit(0) 79 | 80 | @staticmethod 81 | def dir_check() -> None: 82 | """目录检测""" 83 | if not REPORTS.exists(): 84 | Path.mkdir(REPORTS) 85 | 86 | if not TMP.exists(): 87 | Path.mkdir(TMP) 88 | 89 | @staticmethod 90 | def update_check(): 91 | """软件更新检测 92 | 93 | :return: 94 | """ 95 | logger.info("Checking for the latest version...") 96 | with Client(verify=False, timeout=3) as c: 97 | try: 98 | response = c.get("https://jammny.github.io/jws/version.txt") 99 | new_version: str = response.text.rstrip() 100 | if new_version == VERSION: 101 | logger.info(f"You are currently using the latest version. {new_version}") 102 | else: 103 | logger.info(f"[y]Found new version: {new_version} —> https://github.com/jammny/jws-cli[/y]") 104 | except: 105 | pass 106 | 107 | @staticmethod 108 | def mod_check(): 109 | """mod模块检测 110 | 111 | :return: 112 | """ 113 | for i in THIRDPARTY_APP.values(): 114 | if not i.exists(): 115 | logger.error(f"[y]Some features will not be available, because '{i}' is missing.") 116 | 117 | def run(self): 118 | self.py_version_check() 119 | self.dir_check() 120 | self.mod_check() 121 | self.update_check() 122 | -------------------------------------------------------------------------------- /lib/core/dingding.py: -------------------------------------------------------------------------------- 1 | from dingtalkchatbot.chatbot import DingtalkChatbot 2 | 3 | from lib.core.log import logger 4 | 5 | 6 | def dingtalk_robot(webhook: str, secret: str, text: str): 7 | try: 8 | bot = DingtalkChatbot(webhook, secret) 9 | bot.send_markdown( 10 | title=f'来自JWS的推送', 11 | text=text, 12 | is_at_all=True 13 | ) 14 | logger.info("Dingding sent successfully!") 15 | except Exception as e: 16 | logger.error(f"[red]DingDing sending failed! {e}[/red]") 17 | -------------------------------------------------------------------------------- /lib/core/log.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:日志模块。 6 | """ 7 | import logging 8 | from pathlib import Path 9 | 10 | from rich.console import Console 11 | from rich.logging import RichHandler 12 | from rich.theme import Theme 13 | 14 | from lib.core.settings import CONFIG_DATA, LOG 15 | from lib.utils.tools import get_time 16 | 17 | 18 | LEVEL = "DEBUG" if CONFIG_DATA['debug_mode'] else "INFO" 19 | 20 | custom_theme = Theme({ 21 | "g": "bold green", 22 | "y": "bold yellow", 23 | "red": "bold red" 24 | }) 25 | 26 | console = Console(theme=custom_theme, color_system="auto", highlight=False) 27 | 28 | # 配置rich记录器 # 29 | logging.basicConfig( 30 | level=LEVEL, 31 | format="%(message)s", 32 | handlers=[ 33 | RichHandler( 34 | show_time=True, 35 | console=console, 36 | omit_repeated_times=False, 37 | markup=True, 38 | log_time_format="[%H:%M:%S]" 39 | ) 40 | ] 41 | ) 42 | 43 | logger = logging.getLogger("rich") 44 | 45 | # 配置日志文件输出 # 46 | FORMAT = logging.Formatter("%(asctime)s - %(name)s - %(levelname)-9s - " 47 | "%(filename)-8s : %(lineno)s line - %(message)s", 48 | datefmt="%Y/%m/%d %H:%M:%S") 49 | if not LOG.exists(): 50 | Path.mkdir(LOG) 51 | LOGGERFILE = logging.FileHandler(filename=f"{LOG}/{get_time()}.log", mode='w', encoding='utf-8') 52 | LOGGERFILE.setLevel(LEVEL) 53 | LOGGERFILE.setFormatter(FORMAT) 54 | logger.addHandler(LOGGERFILE) 55 | -------------------------------------------------------------------------------- /lib/core/report.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 生成报告核心代码 6 | """ 7 | from os import mkdir 8 | from pathlib import Path 9 | from typing import List 10 | 11 | from jinja2 import Environment, FileSystemLoader 12 | 13 | from tinydb import TinyDB 14 | from tinydb.table import Document 15 | 16 | from lib.core.settings import REPORTS 17 | from lib.core.settings import TMP 18 | from lib.utils.encrypt import GetKey 19 | from lib.core.log import logger 20 | 21 | 22 | class Report: 23 | def __init__(self, target: str = None): 24 | """ 25 | 26 | :param target: 数据库名字 27 | """ 28 | # 目录命名 # 29 | if target: 30 | self.target = target 31 | else: 32 | self.target = f"{GetKey().random_key(6)}" 33 | 34 | # 如果目录不存在就创建 # 35 | self.tmp_dir: Path = Path(f"{TMP}/{self.target}") 36 | if not self.tmp_dir.exists(): 37 | mkdir(self.tmp_dir) 38 | 39 | # 初始化数据库 40 | self.db = TinyDB(f"{self.tmp_dir}/{self.target}.json") 41 | # logger.info(f"Output files:{self.tmp_dir}/{target}.json") 42 | 43 | def write_report(self, data): 44 | env = Environment(loader=FileSystemLoader('db')) 45 | template = env.get_template('report.html') 46 | with open(f"{REPORTS}/{self.target}.html", 'w', encoding="utf-8") as f: 47 | html_content = template.render(target=self.target, data=data) 48 | f.write(html_content) 49 | 50 | def db_insert(self, key_name: str, value: list) -> List[Document]: 51 | """写入数据 52 | 53 | :param key_name: 54 | :param value: 55 | :return: 56 | """ 57 | db = self.db 58 | groups = db.table(key_name) 59 | for i in value: 60 | groups.insert(i) 61 | return groups.all() 62 | 63 | def db_select(self, db): 64 | """ 65 | 读取数据库中的所有数据 66 | :return: 67 | """ 68 | groups = db.tables() 69 | return {i: db.table(i).all() for i in groups} 70 | 71 | def write_txt(self, file_name, data): 72 | """部分结果写入tmp供其他程序调用 73 | 74 | :param file_name: 文件名 75 | :param data: 文件内容 76 | :return: 77 | """ 78 | # 写入txt # 79 | try: 80 | with open(f'{self.tmp_dir}/{file_name}.txt', encoding="utf-8", mode="w") as f: 81 | f.write("\n".join(data)) 82 | logger.info(f"Output files:{self.tmp_dir}/{file_name}.txt") 83 | except Exception as e: 84 | logger.error(f"File write failure. {e}") 85 | 86 | def html(self): 87 | db = self.db 88 | groups = db.tables() 89 | data = {i: db.table(i).all() for i in groups} 90 | # 将需要渲染的数据写入模板 # 91 | self.write_report(data) 92 | 93 | def run(self, key_name: str, value: list): 94 | """ 95 | 96 | :param key_name: 数据库的键 97 | :param value: 数据库的对应的值,这里传入需要写入的列表数据 98 | :return: 99 | """ 100 | # 将数据写入数据库 # 101 | self.db_insert(key_name, value) 102 | -------------------------------------------------------------------------------- /lib/core/settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:程序常量数据配置 6 | """ 7 | import platform 8 | from pathlib import Path 9 | 10 | import yaml 11 | 12 | 13 | # 版本信息 # 14 | VERSION: str = "0.2.0" 15 | 16 | # 操作系统信息 # 17 | OSNAME: str = platform.system() 18 | 19 | # 当前工作目录 # 20 | DIRNAME: Path = Path.cwd() 21 | 22 | # Banner信息 # 23 | BANNER: str = ( 24 | "[bold red] ___ _ _ _____ _____ _ _____ \n" 25 | " |_ || | | / ___| / __ \| | |_ _|\n" 26 | " | || | | \ `--. ______| / \/| | | | \n" 27 | " | || |/\| |`--. \______| | | | | | \n" 28 | "/\__/ /\ /\ /\__/ / | \__/\| |_____| |_ \n" 29 | "\____/ \/ \/\____/ \____/\_____/\___/ [/bold red]\n" 30 | "\n" 31 | f"https://github.com/jammny/jws-cli {VERSION}\n" 32 | ) 33 | 34 | # 定义各种目录路径 # 35 | LOG = DIRNAME / "log" 36 | WAF_PLUGINS = DIRNAME / "db/plugins/waf" # 插件目录 37 | REPORTS: Path = DIRNAME / "reports" # 报告输出目录 38 | TMP: Path = DIRNAME / "reports" # 缓存目录 39 | THIRDPARTY_PATH = DIRNAME / "thirdparty" # 第三方程序目录 40 | CONFIG_PATH = DIRNAME / "db/config.yaml" # 配置文件目录 41 | FINGER: Path = DIRNAME / "db/finger.json" # 指纹库路径 42 | 43 | # 读取配置数据 # 44 | try: 45 | with open(CONFIG_PATH, mode="r", encoding="utf-8") as f: 46 | CONFIG_DATA = yaml.load(f.read(), Loader=yaml.FullLoader) 47 | except Exception as e: 48 | raise Exception 49 | 50 | # 数据表格展示 51 | SHOW_TABLE: bool = CONFIG_DATA['show_table'] 52 | 53 | # 自动化扫描配置 # 54 | AUTO_SETTING: dict = CONFIG_DATA['auto_setting'] 55 | SMART_MODE: bool = AUTO_SETTING['smart_mode'] 56 | 57 | # 邮箱配置 # 58 | SEND_EMAIL = AUTO_SETTING['send_email'] 59 | SEND_PASS = AUTO_SETTING['send_pass'] 60 | REC_EMAIL = AUTO_SETTING['rec_email'] 61 | SMTP_SERVER = AUTO_SETTING['smtp_server'] 62 | SMTP_PORT = AUTO_SETTING['smtp_port'] 63 | 64 | # 子域名模块 # 65 | SUB_CONFIG: dict = CONFIG_DATA['sub_scan'] 66 | SUBNAMES: Path = DIRNAME / 'db/dictionary/subnames.txt' 67 | SUBWORIDS: Path = DIRNAME / 'db/dictionary/subwords.txt' 68 | DNS_DATASETS_PATH: Path = DIRNAME / "db/subdomain" 69 | QQWRYPATH: Path = DIRNAME / "db/qqwry.dat" # 纯真ip数据库路径 70 | BRUTE_FUZZY = SUB_CONFIG["brute_fuzzy"] 71 | BRUTE_ENGINE = SUB_CONFIG["brute_engine"] 72 | API_KEY: dict = CONFIG_DATA["api_key"] 73 | 74 | # 端口扫描模块 # 75 | PORT_CONFIG: dict = CONFIG_DATA['port_scan'] 76 | 77 | # C段扫描模块 78 | CIDR_CONFIG: dict = CONFIG_DATA['cidr_scan'] 79 | 80 | # POC模块 81 | POC_CONFIG: dict = CONFIG_DATA['poc_scan'] 82 | 83 | # 第三方模块 # 84 | THIRDPARTY_APP: dict = { 85 | "afrog": THIRDPARTY_PATH / "afrog.exe" if OSNAME == "Windows" else THIRDPARTY_PATH / "afrog", 86 | "ksubdomain": THIRDPARTY_PATH / "ksubdomain.exe" if OSNAME == "Windows" else THIRDPARTY_PATH / "ksubdomain", 87 | "nimscan": THIRDPARTY_PATH / "nimscan.exe" if OSNAME == "Windows" else THIRDPARTY_PATH / "nimscan", 88 | } 89 | -------------------------------------------------------------------------------- /lib/modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jammny/jws-cli/b454bd0cdae2fae7a086485375dba8b419d6ad05/lib/modules/__init__.py -------------------------------------------------------------------------------- /lib/modules/auto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jammny/jws-cli/b454bd0cdae2fae7a086485375dba8b419d6ad05/lib/modules/auto/__init__.py -------------------------------------------------------------------------------- /lib/modules/auto/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:此模块专门用于处理数据。 6 | """ 7 | from re import compile as re_compile, Pattern 8 | from typing import AnyStr, List 9 | 10 | 11 | def distinguish_between_ip(ip_list: List[str]) -> dict: 12 | """区分内网IP和外网IP 13 | 14 | :param ip_list: [IP地址] 15 | :return: dict 16 | """ 17 | # 先对输入的列表进行去重 18 | ip_list = set(ip_list) 19 | rex: Pattern[AnyStr] = re_compile('^(127\\.0\\.0\\.1)|(localhost)|(10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})|(172\\.((1[6-9])|(2\\d)|' 20 | '(3[01]))\\.\\d{1,3}\\.\\d{1,3})|(192\\.168\\.\\d{1,3}\\.\\d{1,3})$') 21 | internal_network_ip: list = [rex.search(i).group() for i in ip_list if rex.search(i)] 22 | external_network_ip: list = [i for i in ip_list if not rex.search(i)] 23 | # print(internal_network_ip) 24 | # print(external_network_ip) 25 | return { 26 | 'internal_network_ip': internal_network_ip, 27 | 'external_network_ip': external_network_ip 28 | } 29 | -------------------------------------------------------------------------------- /lib/modules/cdn/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jammny/jws-cli/b454bd0cdae2fae7a086485375dba8b419d6ad05/lib/modules/cdn/__init__.py -------------------------------------------------------------------------------- /lib/modules/cdn/cdn_scan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 利用域名解析结果 快速判断CDN使用情况。 6 | """ 7 | from typing import Optional, List 8 | 9 | from lib.core.settings import QQWRYPATH 10 | from lib.modules.cdn.qqwry import QQwry 11 | 12 | from lib.modules.cdn.dns_resolver import DnsResolver 13 | 14 | 15 | class CdnScan(object): 16 | def __init__(self,) -> None: 17 | self.cdn_result: list = [] # 存放扫描结果 18 | self.qqwry: QQwry = QQwry() 19 | self.qqwry.load_file(str(QQWRYPATH)) 20 | 21 | def query_address(self, ip: list) -> list: 22 | """查询物理地址 23 | 24 | :param ip: 需要查询物理地址的IP列表 25 | :return: 26 | """ 27 | return [self.qqwry.lookup(i) for i in ip] 28 | 29 | def dns_lookup(self, domain_list: List[str], thread_count=100) -> None: 30 | """利用dns解析域名 31 | 32 | :param thread_count: 33 | :param domain_list: [需要解析的域名] 34 | :return: 35 | """ 36 | def func(data) -> Optional[dict]: 37 | domain: str = data[0] 38 | ip: list = data[1] 39 | address: List[list] = self.query_address(ip) # 查询IP物理地址 40 | cdn: str = "true" if len(ip) > 1 else "" 41 | return { 42 | "subdomain": domain, 43 | "ip": ip, 44 | "cdn": cdn, 45 | "address": address, 46 | 'method': "brute" 47 | } 48 | 49 | dns_results: List[tuple] = DnsResolver().run(targets_list=list(domain_list), thread_count=thread_count) 50 | 51 | if dns_results: # 对解析的数据进行有效性筛选 52 | self.cdn_result = list(map(func, dns_results)) 53 | 54 | def run(self, target_list: List[str], thread_count=100) -> List[dict]: 55 | """类执行入口 56 | 57 | :param thread_count: 58 | :param target_list: [域名,...] 59 | :return: [扫描结果,...] 60 | """ 61 | self.dns_lookup(target_list, thread_count) 62 | return self.cdn_result 63 | -------------------------------------------------------------------------------- /lib/modules/cdn/dns_resolver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: DNS解析 6 | """ 7 | import asyncio 8 | from queue import Queue 9 | 10 | import dns.resolver 11 | 12 | from lib.utils.thread import threadpool_task 13 | 14 | 15 | class DnsResolver(object): 16 | def __init__(self): 17 | self.dns_results = [] 18 | 19 | def resolve_dns(self, queue_obj: Queue) -> None: 20 | try: 21 | hostname: str = queue_obj.get() 22 | answers = dns.resolver.resolve(hostname, 'A') 23 | ip: list = [str(rdata) for rdata in answers] 24 | self.dns_results.append((hostname, ip)) 25 | except Exception as e: 26 | pass 27 | 28 | def run(self, targets_list: list, thread_count=100) -> list: 29 | threadpool_task(task=self.resolve_dns, queue_data=targets_list, thread_count=thread_count) 30 | return self.dns_results 31 | 32 | 33 | class AsyncDnsResolver(object): 34 | """异步批量DNS解析, 在windwos系统下解析IP正常,在linux下解析会出现IPV6的格式。。。 35 | 36 | """ 37 | async def resolve_dns(self, hostname): 38 | try: 39 | loop = asyncio.get_event_loop() 40 | result: list = await loop.getaddrinfo(hostname, 80, family=0, type=0, proto=0, flags=0) 41 | # print(hostname, result) 42 | return hostname, result 43 | except: 44 | return hostname, None 45 | 46 | async def main(self, domains): 47 | tasks = [asyncio.create_task(self.resolve_dns(hostname)) for hostname in domains] 48 | results = await asyncio.gather(*tasks) 49 | return results 50 | 51 | 52 | if __name__ == "__main__": 53 | app = AsyncDnsResolver() 54 | targets = ['www.python.org', 'jammmny.com', 'baidu.com'] 55 | print(asyncio.run(app.main(targets))) 56 | -------------------------------------------------------------------------------- /lib/modules/cdn/qqwry/__init__.py: -------------------------------------------------------------------------------- 1 | from .qqwry import QQwry 2 | from .cz88update import updateQQwry 3 | 4 | __version__ = '1.2.1' 5 | 6 | __all__ = ('QQwry', 'updateQQwry') 7 | 8 | __doc__ = 'Document of this module: https://pypi.org/project/qqwry-py3/' -------------------------------------------------------------------------------- /lib/modules/cdn/qqwry/cz88update.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # 3 | # 用于从纯真网络(cz88.net)更新qqwry.dat 4 | # for Python 3.0+ 5 | # 来自 https://pypi.python.org/pypi/qqwry-py3 6 | # 7 | # 用法: 8 | # from cz88update import updateQQwry 9 | # result = updateQQwry(filename) 10 | # 11 | # 当参数filename是str类型时,表示要保存的文件名。 12 | # 成功后返回一个正整数,是文件的字节数;失败则返回一个负整数。 13 | # 14 | # 当参数filename是None时,函数直接返回qqwry.dat的文件内容(一个bytes对象)。 15 | # 成功后返回一个bytes对象;失败则返回一个负整数。这里要判断一下返回值的类型是bytes还是int。 16 | # 17 | # 负整数表示的错误: 18 | # -1:下载copywrite.rar时出错 19 | # -2:解析copywrite.rar时出错 20 | # -3:下载qqwry.rar时出错 21 | # -4:qqwry.rar文件大小不符合copywrite.rar的数据 22 | # -5:解压缩qqwry.rar时出错 23 | # -6:保存到最终文件时出错 24 | 25 | import struct 26 | import urllib.request 27 | import zlib 28 | import logging 29 | from typing import Union 30 | 31 | __all__ = ('updateQQwry',) 32 | 33 | logger = logging.getLogger(__name__) 34 | 35 | def updateQQwry(filename: Union[str, None]) -> Union[int, bytes]: 36 | '''1.当参数filename是str类型时,表示要保存的文件名。 37 | 成功后返回一个正整数,是文件的字节数;失败则返回一个负整数。 38 | 39 | 2.当参数filename是None时,函数直接返回qqwry.dat的文件内容(一个bytes对象)。 40 | 成功后返回一个bytes对象;失败则返回一个负整数。 41 | 这里要判断一下返回值的类型是bytes还是int。''' 42 | def get_fetcher(): 43 | # no proxy 44 | proxy = urllib.request.ProxyHandler({}) 45 | # opener 46 | opener = urllib.request.build_opener(proxy) 47 | 48 | def open_url(file_name, url): 49 | # request对象 50 | headers = { 51 | 'User-Agent': 'Mozilla/3.0 (compatible; Indy Library)', 52 | 'Host': 'update.cz88.net' 53 | } 54 | req = urllib.request.Request(url, headers=headers) 55 | 56 | try: 57 | # r是HTTPResponse对象 58 | r = opener.open(req, timeout=60) 59 | dat = r.read() 60 | if not dat: 61 | raise Exception('文件大小为零') 62 | return dat 63 | except Exception as e: 64 | logger.error('下载%s时出错: %s' % (file_name, str(e))) 65 | return None 66 | 67 | return open_url 68 | 69 | fetcher = get_fetcher() 70 | 71 | # download copywrite.rar 72 | url = 'http://update.cz88.net/ip/copywrite.rar' 73 | data = fetcher('copywrite.rar', url) 74 | if not data: 75 | return -1 76 | 77 | # extract infomation from copywrite.rar 78 | if len(data) <= 24 or data[:4] != b'CZIP': 79 | logger.error('解析copywrite.rar时出错') 80 | return -2 81 | 82 | version, unknown1, size, unknown2, key = \ 83 | struct.unpack_from('<IIIII', data, 4) 84 | if unknown1 != 1: 85 | logger.error('解析copywrite.rar时出错') 86 | return -2 87 | 88 | # download qqwry.rar 89 | url = 'http://update.cz88.net/ip/qqwry.rar' 90 | data = fetcher('qqwry.rar', url) 91 | if not data: 92 | return -3 93 | 94 | if size != len(data): 95 | logger.error('qqwry.rar文件大小不符合copywrite.rar的数据') 96 | return -4 97 | 98 | # decrypt 99 | head = bytearray(0x200) 100 | for i in range(0x200): 101 | key = (key * 0x805 + 1) & 0xff 102 | head[i] = data[i] ^ key 103 | data = head + data[0x200:] 104 | 105 | # decompress 106 | try: 107 | data = zlib.decompress(data) 108 | except: 109 | logger.error('解压缩qqwry.rar时出错') 110 | return -5 111 | 112 | if filename == None: 113 | return data 114 | elif type(filename) == str: 115 | # save to file 116 | try: 117 | with open(filename, 'wb') as f: 118 | f.write(data) 119 | return len(data) 120 | except: 121 | logger.error('保存到最终文件时出错') 122 | return -6 123 | else: 124 | logger.error('保存到最终文件时出错') 125 | return -6 126 | 127 | if __name__ == '__main__': 128 | import sys 129 | if len(sys.argv) > 1: 130 | ret = updateQQwry(sys.argv[1]) 131 | if ret > 0: 132 | print('成功更新到%s,%s字节' % 133 | (sys.argv[1], format(ret, ',')) 134 | ) 135 | else: 136 | print('更新失败,错误代码:%d' % ret) 137 | else: 138 | print('用法:以想要保存的文件名作参数。') 139 | -------------------------------------------------------------------------------- /lib/modules/cdn/qqwry/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jammny/jws-cli/b454bd0cdae2fae7a086485375dba8b419d6ad05/lib/modules/cdn/qqwry/py.typed -------------------------------------------------------------------------------- /lib/modules/cidr/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | -------------------------------------------------------------------------------- /lib/modules/cidr/cidr_scan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 利用协程写了一个端口扫描模块。 6 | """ 7 | from collections import Counter 8 | from typing import Tuple 9 | 10 | from IPy import IP 11 | 12 | from .thirdparty import CidrFofa 13 | from .cidr_system import CidrSystem 14 | 15 | from lib.core.log import logger 16 | 17 | from lib.core.settings import CIDR_CONFIG, SHOW_TABLE 18 | from rich.console import Console 19 | from rich.table import Table 20 | 21 | 22 | class Cidr: 23 | def __init__(self, engine: str): 24 | self.engine: str = engine 25 | self.ip: list = [] # 存放存活ip 26 | self.cidr_results: list = [] # 存放结果 27 | self.cidr_counter = [] # 统计C段划分 28 | 29 | def format_cidr(self, target_list: list, auto=False): 30 | """格式化IP信息 整理划分C段 31 | 32 | :param auto: 33 | :param target_list: 34 | :return: 35 | """ 36 | def func(item): 37 | if "/24" not in item: 38 | ip_mask = IP(f"{item}/255.255.255.0", make_net=True) # 将IP转成cidr的格式 39 | return str(ip_mask) 40 | else: 41 | return item 42 | cdir = [func(i) for i in target_list] 43 | # 统计cidr出现的次数 44 | res = [] 45 | 46 | for c in Counter(cdir).items(): 47 | logger.info(f"cidr: {c[0]}, occurrence number:{c[1]}") 48 | self.cidr_counter.append(f"cidr: {c[0]}, occurrence number:{c[1]}") 49 | # 如果大于设置的阈值,就添加进入目标 50 | if c[1] >= CIDR_CONFIG['occurrence_limit'] and auto: 51 | res.append(c[0]) 52 | elif c[1] < CIDR_CONFIG['occurrence_limit'] and auto: 53 | pass 54 | else: 55 | res.append(c[0]) 56 | logger.info(f"According to the set threshold, the cidr range to be scanned is: {res}") 57 | return res 58 | 59 | def show_table(self): 60 | """表格展示数据 61 | 62 | :return: 63 | """ 64 | data = self.cidr_results 65 | table = Table(title="cdn results", show_lines=False) 66 | table.add_column("host", justify="left", style="cyan", no_wrap=True) 67 | table.add_column("port", justify="left", style="magenta") 68 | table.add_column("protocol", justify="left", style="red") 69 | table.add_column("banner", justify="left", style="red") 70 | for i in data: 71 | table.add_row(i['host'], str(i['port']), i['protocol'], i['banner']) 72 | console = Console() 73 | console.print(table) 74 | 75 | def run(self, target_list: list, auto=False) -> Tuple[list, list]: 76 | """类执行入口 77 | 78 | :param auto: 79 | :param target_list: 80 | :return: 81 | """ 82 | engine = self.engine 83 | 84 | if not target_list: 85 | logger.info(f"No target input: {target_list}") 86 | return self.cidr_results, self.cidr_counter 87 | else: 88 | target_list = list(set(target_list)) # 去重 89 | 90 | logger.info(f"[g]Current task: CidrScan | Target numbers: {len(target_list)} | Engine: {engine} |[/g]") 91 | 92 | # 首先将IP整理成C段 93 | cidr: list = self.format_cidr(target_list, auto) 94 | 95 | if engine == 'fofa': 96 | self.cidr_results = CidrFofa().run(cidr) 97 | # if not self.cidr_results: # 如果fofa不能用,就用系统默认扫描方法。 98 | # self.cidr_results = CidrSystem().run(cidr) 99 | elif engine == 'system': 100 | self.cidr_results = CidrSystem().run(cidr) 101 | 102 | if SHOW_TABLE: 103 | self.show_table() 104 | 105 | logger.info(f"CIDR task finished! Effective collection quantity: {len(self.cidr_results)}") 106 | return self.cidr_results, self.cidr_counter 107 | -------------------------------------------------------------------------------- /lib/modules/cidr/cidr_system.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 基于主机发现技术,识别网段中存货的IP 6 | """ 7 | import ipaddress 8 | 9 | from lib.core.settings import PORT_CONFIG 10 | from lib.core.log import logger 11 | from lib.modules.port.port_scan import PortScan 12 | 13 | 14 | class CidrSystem(object): 15 | def __init__(self): 16 | self.cidr_results: list = [] 17 | 18 | def run(self, cidr: list): 19 | """类执行入口 20 | 21 | :param cidr: 22 | :return: 23 | """ 24 | for c in cidr: 25 | logger.info(f"Scanner {c}...") 26 | ip_list: list = [str(x) for x in ipaddress.ip_network(c).hosts()] # 添加对应的C段IP数 27 | hosts: list = ip_list 28 | if hosts: 29 | port_range = PORT_CONFIG['port_range'] 30 | engine = PORT_CONFIG['engine'] 31 | banner_status = PORT_CONFIG['banner_status'] 32 | s = PortScan(port_range, engine, banner_status) 33 | port_results = s.run(hosts) 34 | for i in port_results: 35 | i['cidr'] = c 36 | self.cidr_results.append(i) 37 | return self.cidr_results 38 | 39 | 40 | if __name__ == "__main__": 41 | pass -------------------------------------------------------------------------------- /lib/modules/cidr/thirdparty.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: fofa调用程序 6 | """ 7 | from queue import Queue 8 | 9 | from lib.modules.search.api_fofa import Fofa 10 | from lib.core.log import logger 11 | from lib.utils.thread import threadpool_task 12 | 13 | 14 | class CidrFofa(object): 15 | def __init__(self): 16 | self.cidr_results = [] 17 | 18 | def fofa_request(self, queue: Queue): 19 | """使用fofa api 来收集C段信息 20 | 21 | :return: 22 | """ 23 | cidr = queue.get() 24 | query: str = f'ip="{cidr}"&&(protocol="http"||protocol="https")' 25 | fofa_results: dict = Fofa(query).run() 26 | tmp_results = [{"cidr": cidr, "host": item[2], "port": item[3], "protocol": item[4], "banner": item[6][:20]} 27 | for item in fofa_results] 28 | self.cidr_results += tmp_results 29 | 30 | def run(self, cidr: list) -> list: 31 | """类执行入口 32 | 33 | :param cidr: 目标cidr格式的列表 34 | :return: 35 | """ 36 | logger.info("Trying to use fofa.") 37 | threadpool_task(task=self.fofa_request, queue_data=cidr, thread_count=1) 38 | return self.cidr_results 39 | 40 | 41 | if __name__ == "__main__": 42 | pass 43 | -------------------------------------------------------------------------------- /lib/modules/company/company_scan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 6 | """ 7 | from typing import List 8 | 9 | from lib.core.log import logger 10 | from lib.modules.company.icp import ICP 11 | 12 | 13 | class CompanyScan(object): 14 | def run(self, company_name) -> List[str]: 15 | logger.info(f"[g]| Current task: CompanyScan | Name: {company_name} |[/g]") 16 | results = ICP().run(company_name) 17 | return results 18 | -------------------------------------------------------------------------------- /lib/modules/company/icp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 利用多线程快速识别指纹信息。 6 | """ 7 | 8 | from httpx import Client 9 | from parsel import Selector 10 | 11 | from lib.core.log import logger 12 | from lib.utils.tools import domain_format 13 | 14 | 15 | class ICP(object): 16 | def __init__(self): 17 | pass 18 | 19 | def run(self, company_name): 20 | results = set() 21 | url = f"https://www.beianx.cn/search/{company_name}" 22 | try: 23 | with Client() as c: 24 | response = c.get(url) 25 | response_body = response.text 26 | selector = Selector(response_body) 27 | data_list: list = selector.css('table[class="table table-sm table-bordered table-hover"] a::text').getall() 28 | # print(data_list) 29 | for domain in data_list: 30 | if domain_format(domain): 31 | # print(domain) 32 | results.add(domain) 33 | except Exception as e: 34 | logger.error(f"[red]{url} {e}[/red]") 35 | 36 | logger.info(f"Target: {results} be found.") 37 | # print(results) 38 | return results -------------------------------------------------------------------------------- /lib/modules/finger/Wappalyzer/__init__.py: -------------------------------------------------------------------------------- 1 | from .Wappalyzer import WebPage, Wappalyzer 2 | 3 | Wappalyzer = Wappalyzer 4 | WebPage = WebPage 5 | -------------------------------------------------------------------------------- /lib/modules/finger/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | -------------------------------------------------------------------------------- /lib/modules/poc/poc_scan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 6 | """ 7 | from typing import List 8 | 9 | from lib.modules.poc.thirdparty import afrog 10 | from lib.core.log import logger 11 | 12 | 13 | class PocScan: 14 | def __init__(self, engine): 15 | self.engine = engine 16 | self.poc_results = [] 17 | 18 | def run(self, targets_list: list, target=None) -> List[dict]: 19 | """ 20 | 21 | :param target: 自动化扫描的时候,传入域名。 22 | :param targets_list: 23 | :return: 24 | """ 25 | logger.info(f"Current task: PocScan | Target number: {len(targets_list)} | Engine: afrog") 26 | engine = self.engine 27 | if engine == "afrog": 28 | self.poc_results = afrog(targets_list, target) 29 | return self.poc_results 30 | -------------------------------------------------------------------------------- /lib/modules/poc/thirdparty.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 6 | """ 7 | from os import system 8 | 9 | from pathlib import Path 10 | from typing import Optional 11 | 12 | from lib.core.settings import TMP, THIRDPARTY_APP 13 | from lib.utils.encrypt import GetKey 14 | from lib.utils.file import read_json_file, write_txt 15 | from lib.core.log import logger 16 | 17 | 18 | def make_dir(target: Optional[str]) -> tuple: 19 | """创建目录 20 | 21 | :param target: 自动化扫描时候的目标 22 | :return: 23 | """ 24 | path_name: str = target if target else GetKey().random_key(5) 25 | new_path = TMP / path_name 26 | if not new_path.exists(): 27 | Path.mkdir(new_path) 28 | return path_name, new_path 29 | 30 | 31 | def afrog(target_list: list, target=None) -> list: 32 | """afrog漏洞扫描 33 | 34 | :param target_list: 待扫描的目标列表 35 | :param target: 自动化扫描时候的目标 36 | :return: 37 | """ 38 | poc_results: list = [] 39 | 40 | # 将需要扫描的URL写入指定目录下的txt文件,用于afrog读取 # 41 | path_name, new_path = make_dir(target) 42 | target_path: str = f"{new_path}/poc_targets.txt" 43 | write_status: bool = write_txt(target_path, target_list) 44 | 45 | if not write_status: 46 | return poc_results 47 | 48 | app: str = THIRDPARTY_APP['afrog'] 49 | output: str = f"{new_path}/poc_afrog" 50 | command: str = f"{app} -T {target_path} -o {output}.html -j {output}.json" 51 | system(command) 52 | afrog_results: list = read_json_file(f"{output}.json") 53 | if not afrog_results: 54 | return poc_results 55 | 56 | logger.info(f"Output: {output}.json") 57 | for i in afrog_results: 58 | try: 59 | url = i['fulltarget'] 60 | except: 61 | url = "" 62 | poc_results.append({ 63 | 'id': i['pocinfo']['id'], 64 | 'name': i['pocinfo']['infoname'], 65 | 'seg': i['pocinfo']['infoseg'], 66 | 'url': url, 67 | }) 68 | 69 | return poc_results 70 | -------------------------------------------------------------------------------- /lib/modules/port/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | -------------------------------------------------------------------------------- /lib/modules/port/async_scan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:异步端口扫描实现 6 | """ 7 | import asyncio 8 | 9 | from rich.progress import Progress 10 | 11 | from lib.core.log import logger 12 | 13 | 14 | class AsyncScan: 15 | """异步TCP扫描""" 16 | def __init__(self, host: str, portlist: list) -> None: 17 | self.ip: str = host # 目标IP 18 | self.portlist: list = portlist # 待扫描的端口列表 19 | self.thread_count: int = 1000 # 并发数 20 | self.open_port = set() # 开放的端口 21 | 22 | async def main(self, progress, task) -> None: 23 | """异步任务执行 24 | 25 | :return: 26 | """ 27 | thread_count = self.thread_count 28 | sem = asyncio.Semaphore(thread_count) # 设置并发数, 这里sem不要放到全局中去 29 | tasks: list = [self.tcp_scan(port, sem, progress, task) for port in self.portlist] 30 | await asyncio.gather(*tasks) # 开启并发任务 31 | 32 | async def tcp_scan(self, port, sem, progress, task) -> None: 33 | """socket 实现端口扫描 34 | 35 | :param task: 36 | :param progress: 37 | :param sem: 设置并发数 38 | :param port: 目标端口 39 | :return: 40 | """ 41 | try: 42 | async with sem: 43 | reader, writer = await asyncio.wait_for(asyncio.open_connection(self.ip, port), timeout=5.0) 44 | # 关闭连接 # 45 | writer.close() 46 | await writer.wait_closed() 47 | self.open_port.add(port) 48 | progress.console.print(f"{self.ip}:{port}") 49 | except: 50 | pass 51 | finally: 52 | progress.update(task, advance=1) 53 | 54 | def run(self, ) -> set: 55 | """任务执行入口 56 | 57 | :return: 58 | """ 59 | logger.info("Async socket scan...") 60 | with Progress(transient=True) as progress: 61 | task = progress.add_task("[green]Scanning...", total=len(self.portlist)) # 定义一个进度条对象 62 | asyncio.run(self.main(progress, task)) 63 | return self.open_port 64 | -------------------------------------------------------------------------------- /lib/modules/port/check_overflow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:扫描过程中,经常会遇到某些防火墙在单个ip上开放成千上万个端口,实际上是没有资产的,此模块用于解决端口泛滥的问题。 6 | """ 7 | import socket 8 | 9 | __all__ = ['CheckOverFlow', ] 10 | 11 | from lib.core.log import logger 12 | from lib.utils.thread import threadpool_task 13 | 14 | 15 | class CheckOverFlow: 16 | def __init__(self): 17 | self.threshold_value = 6 # 阈值 18 | self.portlist = [1, 2, 3, 4, 5, 65535, 65534, 65533, 65532, 65531, 2000, 2001, 2002, 2003, 2004, 2005, 10000, 19 | 10001, 10002, 10003, 10004, 10005] # 冷门端口 20 | # 可能还需要加个常见端口?有些会特地弄一些常见的端口开放,扰乱扫描结果?后续根据实战经验再看看要不要加。 21 | self.port_results = [] 22 | 23 | def scan_port(self, host: str, queue_obj) -> None: 24 | """ 25 | 26 | :param queue_obj: queue.Queue 27 | :param host: 目标主机 28 | :return: 29 | """ 30 | port = queue_obj.get() 31 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 32 | s.settimeout(5) 33 | result = s.connect_ex((host, port)) 34 | if result == 0: 35 | self.port_results.append(port) 36 | 37 | def run(self, ip) -> bool: 38 | """ 39 | 任务执行入口 40 | :return: 41 | """ 42 | logger.info("Check overflowing...") 43 | threadpool_task(task=self.scan_port, queue_data=self.portlist, task_args=(ip,), thread_count=30) 44 | if len(self.port_results) >= self.threshold_value: 45 | logger.error(f"IP {ip} port is overflowing, Scan has been skipped.") 46 | return True 47 | else: 48 | return False 49 | -------------------------------------------------------------------------------- /lib/modules/port/host_scan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 主机存活扫描 6 | """ 7 | from random import randint 8 | 9 | from scapy.layers.inet import IP, ICMP, TCP, UDP 10 | from scapy.layers.l2 import Ether, ARP 11 | from scapy.all import * 12 | from ping3 import ping 13 | 14 | from lib.core.log import logger 15 | from lib.utils.thread import threadpool_task 16 | 17 | 18 | class HostScan(object): 19 | 20 | def __init__(self): 21 | self.port_resultss: list = [] # 存储IP存活结果 22 | 23 | def start_scan(self, queue_obj: any): 24 | """ 25 | 区别内外网ip 如果是内网IP就使用arp 外网使用ping 26 | :param queue_obj: queue.Queue 27 | :return: 28 | """ 29 | while True: 30 | if queue_obj.empty(): 31 | break 32 | ip: str = queue_obj.get() 33 | if ip[:3] == '10.' or ip[:6] == '172.16' or ip[:6] == '172.31' or ip[:7] == '192.168': 34 | self.arp_scan(ip) 35 | else: 36 | self.ping_scan(ip) 37 | 38 | def arp_scan(self, ip: str) -> None: 39 | """ 40 | 发送arp包 41 | :param ip: 目标IP 42 | :return: 43 | """ 44 | try: 45 | pkt = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=ip) 46 | # 发送arp请求,并获取响应结果。设置3s超时。 47 | res = sr1(pkt, timeout=3, verbose=0) 48 | if res: 49 | self.port_resultss.append(ip) 50 | logger.info(f"Method: ARP, {ip} is alive.") 51 | else: 52 | self.ping_scan(ip) 53 | except: 54 | self.ping_scan(ip) 55 | 56 | def ping_scan(self, ip: str) -> None: 57 | """ 58 | ping方法 59 | :param ip: 目标IP 60 | :return: 61 | """ 62 | if ping(ip): 63 | self.port_resultss.append(ip) 64 | logger.info(f"Method: PING, {ip} is alive.") 65 | else: 66 | self.icmp_scan(ip) 67 | 68 | def icmp_scan(self, ip: str) -> None: 69 | """ 70 | 发送icmp包 防止ping工具没识别到 71 | :param ip: 目标IP 72 | :return: 73 | """ 74 | try: 75 | id_ip = randint(1, 65535) 76 | id_ping = randint(1, 65535) 77 | seq_ping = randint(1, 65535) 78 | pkt = IP(dst=ip, ttl=128, id=id_ip) / ICMP(id=id_ping, seq=seq_ping) / b'hi!' 79 | icmp = sr1(pkt, timeout=3, verbose=False) 80 | if icmp: 81 | logger.info(f"Method: ICMP, {ip} is alive.") 82 | self.port_resultss.append(ip) 83 | else: 84 | self.syn_scan(ip) 85 | except: 86 | self.syn_scan(ip) 87 | 88 | def syn_scan(self, ip: str) -> None: 89 | """ 90 | 发送syn包 91 | :param ip: 目标IP 92 | :return: 93 | """ 94 | try: 95 | # 构造标志位为syn的数据包 96 | pkt = IP(dst=ip) / TCP(dport=80, flags="A") 97 | result = sr1(pkt, timeout=3, verbose=0) 98 | if int(result[TCP].flags) == 4: 99 | self.port_resultss.append(ip) 100 | logger.info(f"Method: SYN, {ip} is alive.") 101 | else: 102 | self.udp_scan(ip) 103 | except: 104 | self.udp_scan(ip) 105 | 106 | def udp_scan(self, ip) -> None: 107 | """ 108 | UDP扫描不准啊 109 | :param ip: 目标IP 110 | :return: 111 | """ 112 | try: 113 | pkt = IP(dst=ip) / UDP(dport=80) 114 | result = sr1(pkt, timeout=3, verbose=0) 115 | # result.show() 116 | # 0x01 代表的ICMP字段值 117 | if int(result[IP].proto) == 0x01: 118 | self.port_resultss.append(ip) 119 | logger.info(f"Method: UDP, {ip} is alive.") 120 | else: 121 | self.port_resultss.append(ip) 122 | pass 123 | # logger.error(f"{ip} is not alive.") 124 | except: 125 | self.port_resultss.append(ip) 126 | pass 127 | # logger.error(f"{ip} is not alive.") 128 | 129 | def run(self, data: list, thread_count: int = 30) -> list: 130 | """ 131 | data: 待识别存活的IP 132 | thread_count: 并发线程数 133 | """ 134 | threadpool_task(task=self.start_scan, queue_data=data, thread_count=thread_count) 135 | return self.port_resultss 136 | -------------------------------------------------------------------------------- /lib/modules/port/thirdparty.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 6 | """ 7 | import re 8 | import subprocess 9 | from typing import Set 10 | 11 | from rich.progress import Progress 12 | 13 | from lib.core.settings import THIRDPARTY_APP 14 | from lib.core.log import logger 15 | 16 | 17 | def nimscan(host) -> Set['int']: 18 | """调用nimscan完成全端口扫描""" 19 | logger.info("Running nimscan...") 20 | app = THIRDPARTY_APP['nimscan'] 21 | command = f'ulimit -n 5500;{app} -i {host}' # 默认扫全端口 22 | 23 | with Progress(transient=True) as progress: 24 | task = progress.add_task("[red]Scanning...", start=False, total=None) 25 | status, res = subprocess.getstatusoutput(command) 26 | progress.update(task, advance=100) 27 | 28 | open_port = set() 29 | if status == 0 and 'Open' in res: 30 | list_1 = res.split('==> ') 31 | for i in list_1: 32 | if 'Open' in i: 33 | a = re.sub("\x1b.*?m", '', i) 34 | b = re.sub("Open", '', a) 35 | c = b.split(' \n')[0] 36 | port = c.split(":")[1] 37 | open_port.add(int(port)) 38 | return open_port 39 | -------------------------------------------------------------------------------- /lib/modules/port/thread_scan.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jammny/jws-cli/b454bd0cdae2fae7a086485375dba8b419d6ad05/lib/modules/port/thread_scan.py -------------------------------------------------------------------------------- /lib/modules/search/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding : utf-8-*- 3 | # coding:unicode_escape 4 | """ 5 | 作者:jammny 6 | 文件描述: 7 | """ 8 | -------------------------------------------------------------------------------- /lib/modules/search/api_binaryedge.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:binaryedge API接口调用 6 | """ 7 | from lib.modules.search.api_base import ApiBase 8 | 9 | 10 | class Binaryedge(ApiBase): 11 | 12 | def __init__(self, domain: str) -> None: 13 | super().__init__() 14 | self.name: str = "Binaryedge" 15 | self.domain: str = domain 16 | self.key: str = self.config['binaryedge_key'] 17 | self.url: str = f"https://api.binaryedge.io/v2/query/domains/subdomain/{domain}" 18 | self.headers: dict = { 19 | 'X-Key': self.key, 20 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0', 21 | } 22 | 23 | -------------------------------------------------------------------------------- /lib/modules/search/api_censys.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding : utf-8-*- 3 | # coding:unicode_escape 4 | """ 5 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 6 | 文件描述:censys api 调用 7 | """ 8 | import base64 9 | from lib.modules.search.api_base import ApiBase 10 | 11 | 12 | class Censys(ApiBase): 13 | 14 | def __init__(self, domain: str) -> None: 15 | super().__init__() 16 | self.domain: str = domain 17 | self.censysID: str = self.config['censys_id'] 18 | self.censysSecret: str = self.config['censys_secret'] 19 | self.name: str = "Censys" 20 | self.url = f"https://search.censys.io/api/v2/certificates/search?per_page=100&virtual_hosts=EXCLUDE" \ 21 | f"&q={domain} " 22 | token = base64.b64encode(f"{self.censysID}:{self.censysSecret}".encode('utf-8')).decode('utf-8') 23 | self.headers = { 24 | 'Authorization': f'Basic {token}', 25 | 'Accept': 'application/json', 26 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) ' 27 | 'Chrome/110.0.0.0 Safari/537.36', 28 | } 29 | -------------------------------------------------------------------------------- /lib/modules/search/api_dnsdumpster.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 内置的DNS数据源模块,爬取 https://www.dnsdumpster.com/ 的数据。 6 | """ 7 | from typing import Union, Set 8 | 9 | from httpx import Client 10 | from tenacity import retry, stop_after_attempt 11 | 12 | from lib.core.log import logger 13 | from lib.modules.search.api_base import ApiBase 14 | from lib.utils.tools import match_subdomains 15 | 16 | 17 | class Dnsdumpster(ApiBase): 18 | 19 | def __init__(self, domain: str): 20 | super().__init__() 21 | self.name: str = "Dnsdumpster" 22 | self.domain: str = domain 23 | self.result_domain = set() 24 | self.url = f"https://dnsdumpster.com/" 25 | self.headers = { 26 | 'Referer': 'https://dnsdumpster.com', 27 | 'Content-Type': 'application/x-www-form-urlencoded', 28 | } 29 | 30 | @retry(stop=stop_after_attempt(2)) 31 | def send_request(self, url) -> Union[str, None]: 32 | """请求接口,返回响应内容 33 | 34 | :return: 35 | """ 36 | headers: dict = self.headers 37 | name: str = self.name 38 | domain: str = self.domain 39 | with Client(verify=False, headers=headers, timeout=10) as c: 40 | c.get(url) 41 | data = { 42 | # 从返回的set-cookies中,拿到csrftoken 43 | 'csrfmiddlewaretoken': c.cookies.get('csrftoken'), 44 | 'targetip': domain, 45 | 'user': 'free' 46 | } 47 | response = c.post(self.url, headers=self.headers, data=data) 48 | if response.status_code == 200: 49 | return response.text 50 | else: 51 | logger.error(f"{name} status_code error: {response} {response.text}") 52 | return 53 | 54 | def get_domain(self) -> Set[str]: 55 | """获取域名数据 56 | 57 | :return: 58 | """ 59 | name: str = self.name 60 | domain: str = self.domain 61 | url: str = self.url 62 | logger.info(f"Running {name}...") 63 | 64 | try: 65 | response_body = self.send_request(url) 66 | except Exception as e: 67 | logger.error(f"{name} connect error! {url} {e}") 68 | return self.result_domain 69 | 70 | if response_body: 71 | self.result_domain = match_subdomains(domain, response_body) 72 | 73 | logger.info(f"Dnsdumpster:{len(self.result_domain)} results found!") 74 | return self.result_domain 75 | -------------------------------------------------------------------------------- /lib/modules/search/api_fofa.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 作者:https://github.com/jammny 5 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 6 | 文件描述:FOFA API接口调用 7 | """ 8 | from base64 import b64encode 9 | from typing import Set, Optional 10 | 11 | from rich.table import Table 12 | 13 | from lib.modules.search.api_base import ApiBase 14 | from lib.core.log import logger 15 | from lib.utils.tools import match_subdomains 16 | 17 | __all__ = ['Fofa'] 18 | 19 | 20 | def show_table(data) -> None: 21 | """表格展示数据 22 | 23 | :return: 24 | """ 25 | if not data: 26 | return 27 | table = Table(title="fofa results", show_lines=False) 28 | table.add_column("host", justify="left", style="cyan", no_wrap=True) 29 | table.add_column("title", justify="left", style="magenta") 30 | table.add_column("ip", justify="left", style="red") 31 | table.add_column("port", justify="left", style="green") 32 | table.add_column("protocol", justify="left", style="green") 33 | for i in data: 34 | table.add_row(i[0], i[1], i[2], i[3], i[4]) 35 | return 36 | 37 | 38 | class Fofa(ApiBase): 39 | 40 | def __init__(self, query: str, domain: Optional[str] = None) -> None: 41 | super().__init__() 42 | self.name = "Fofa" 43 | self.domain: Optional[str] = domain 44 | self.email: str = self.config['fofa_email'] 45 | self.key: str = self.config['fofa_key'] 46 | self.size: str = self.config['fofa_size'] 47 | self.query: str = query 48 | self.qbase64: str = str(b64encode(query.encode("utf-8")), 'utf-8') # fofa查询参数,base64编码 49 | self.url: str = (f"https://fofa.info/api/v1/search/all?email={self.email}&key={self.key}&qbase64={self.qbase64}" 50 | f"&size={self.size}&fields=host,title,ip,port,protocol,domain,banner") 51 | self.results: list = list() 52 | 53 | def get_domain(self,) -> set: 54 | """域名收集专用 55 | 56 | :return: 57 | """ 58 | domain = self.domain 59 | name = self.name 60 | url = self.url 61 | logger.info(f"Running {name}...") 62 | try: 63 | response_json: Optional[dict] = self.send_request(url) 64 | except Exception as e: 65 | logger.error(f"{name} connect error! {url} {e}") 66 | return self.result_domain 67 | 68 | if not response_json: 69 | return self.result_domain 70 | 71 | # 正则提取页面中域名 72 | self.result_domain: Set['str'] = match_subdomains(domain, str(response_json)) 73 | 74 | logger.info(f"{name}:{len(self.result_domain)} results found!") 75 | logger.debug(f"{name}:{self.result_domain}") 76 | return self.result_domain 77 | 78 | def run(self): 79 | url = self.url 80 | name = self.name 81 | 82 | try: 83 | fofa_results = self.send_request(url) 84 | except: 85 | logger.error(f"{name} connect error! Exit the task.") 86 | return list() 87 | 88 | # 判断返回的结果中是否存在error 89 | is_error: bool = fofa_results['error'] 90 | if is_error: 91 | logger.error(f"Fofa Api error! {fofa_results}") 92 | return list() 93 | 94 | # 判断是否有数据 # 95 | results: list = fofa_results['results'] 96 | if not results: 97 | logger.info(f"No information related to the {self.query} was found!") 98 | return list() 99 | 100 | show_table(results) 101 | return results 102 | -------------------------------------------------------------------------------- /lib/modules/search/api_fullhunt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:fullhunt API接口调用 6 | """ 7 | from lib.modules.search.api_base import ApiBase 8 | 9 | 10 | class Fullhunt(ApiBase): 11 | 12 | def __init__(self, domain: str) -> None: 13 | super().__init__() 14 | self.name: str = "Fullhunt" 15 | self.domain: str = domain 16 | self.key: str = self.config['fullhunt_key'] 17 | self.url: str = f"https://fullhunt.io/api/v1/domain/{domain}/subdomains" 18 | self.headers = { 19 | 'X-API-KEY': self.key, 20 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0', 21 | } 22 | 23 | -------------------------------------------------------------------------------- /lib/modules/search/api_hunter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:Hunter API接口调用 6 | """ 7 | from base64 import b64encode 8 | from typing import Set, Optional 9 | 10 | from lib.modules.search.api_base import ApiBase 11 | from lib.core.log import logger 12 | from lib.utils.tools import match_subdomains 13 | 14 | 15 | class Hunter(ApiBase): 16 | def __init__(self, query: str, domain: str) -> None: 17 | super().__init__() 18 | self.name = "Hunter" 19 | self.key = self.config['hunter_key'] 20 | self.query = query # 查询参数 21 | self.domain = domain # 根域名 22 | self.search: str = str(b64encode(query.encode("utf-8")), 'utf-8') # base64编码的参数 23 | self.page_size: int = 20 # 每页返回的最大检索数 24 | self.size: int = self.config['hunter_size'] # # 配置文件设定的最大检索值 25 | self.url: str = (f"https://hunter.qianxin.com/openApi/search?api-key={self.key}&search={self.search}" 26 | f"&page=[page]&page_size={self.page_size}&is_web=1&port_filter=false&status_code=0") 27 | 28 | def get_domain(self) -> Set[str]: 29 | """域名收集专用 30 | 31 | :return: 32 | """ 33 | name = self.name 34 | domain: str = self.domain 35 | size: int = self.size 36 | page_size: int = self.page_size 37 | url = self.url 38 | logger.info(f"Running {name}...") 39 | 40 | # 先获取一页,看看检索出多少数据 # 41 | new_url: str = url.replace("[page]", "1") 42 | try: 43 | response_json: Optional[dict] = self.send_request(new_url) 44 | except Exception as e: 45 | logger.error(f"{name} connect error! {url} {e}") 46 | return self.result_domain 47 | 48 | if not response_json: 49 | return self.result_domain 50 | 51 | # 正则提取页面中域名 52 | self.result_domain: Set['str'] = match_subdomains(domain, str(response_json)) 53 | 54 | # 根据搜索出的检索量和设定配置,循环遍历页数 # 55 | try: 56 | total: int = response_json['data']['total'] 57 | page: int = self.get_page(total, size, page_size) 58 | self.circular_process(page, url, domain) 59 | except Exception as e: 60 | logger.error(f"{response_json} {e}") 61 | 62 | logger.info(f"{name}:{len(self.result_domain)} results found!") 63 | logger.debug(f"{name}:{self.result_domain}") 64 | return self.result_domain 65 | 66 | def run(self): 67 | pass 68 | -------------------------------------------------------------------------------- /lib/modules/search/api_quake.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:360 quake API接口调用 6 | """ 7 | from typing import Optional, Set 8 | 9 | from lib.core.log import logger 10 | from lib.modules.search.api_base import ApiBase 11 | from lib.utils.tools import match_subdomains 12 | 13 | 14 | class Quake(ApiBase): 15 | 16 | def __init__(self, query: str, domain: str) -> None: 17 | super().__init__() 18 | self.name: str = "Quake" 19 | self.domain: str = domain 20 | self.key: str = self.config['quake_key'] 21 | self.url: str = f"https://quake.360.net/api/v3/search/quake_service" 22 | self.headers = { 23 | "X-QuakeToken": self.key, 24 | "Content-Type": "application/json" 25 | } 26 | self.size: int = self.config['quake_size'] 27 | self.data = { 28 | "query": query, 29 | "size": self.size, 30 | "ignore_cache": False, 31 | } 32 | 33 | def get_domain(self,) -> set: 34 | """域名收集专用 35 | 36 | :return: 37 | """ 38 | domain = self.domain 39 | name = self.name 40 | url = self.url 41 | logger.info(f"Running {name}...") 42 | try: 43 | response_json: Optional[dict] = self.send_request(url, method="post") 44 | except Exception as e: 45 | logger.error(f"[r]{name} connect error! {url} {e}[/r]") 46 | return self.result_domain 47 | 48 | if not response_json: 49 | return self.result_domain 50 | 51 | # 正则提取页面中域名 52 | self.result_domain: Set[str] = match_subdomains(domain, str(response_json)) 53 | 54 | logger.info(f"{name}:{len(self.result_domain)} results found!") 55 | return self.result_domain 56 | 57 | def run(self): 58 | pass -------------------------------------------------------------------------------- /lib/modules/search/api_robtex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:通过robtex查询域名的dns解析记录,收集其中的域名信息。如果是['A', 'AAAA']记录,那么需要提取IP,然后再反查IP的域名解析。 6 | """ 7 | from time import sleep 8 | from typing import Optional, Set 9 | 10 | from lib.modules.search.api_base import ApiBase 11 | from lib.core.log import logger 12 | from lib.utils.tools import match_subdomains, match_ip 13 | 14 | 15 | class Robtex(ApiBase): 16 | 17 | def __init__(self, domain: str) -> None: 18 | super().__init__() 19 | self.name: str = "Robtex" 20 | self.domain: str = domain 21 | self.result_domain: set = set() 22 | self.url: str = f"https://freeapi.robtex.com/pdns/forward/{self.domain}" 23 | 24 | def get_domain(self) -> Set[str]: 25 | """获取域名数据 26 | 27 | :return: 28 | """ 29 | name: str = self.name 30 | domain: str = self.domain 31 | url: str = self.url 32 | logger.info(f"Running {name}...") 33 | 34 | try: 35 | response_body: Optional[str] = self.send_request(url, api=False) 36 | except Exception as e: 37 | logger.error(f"{name} connect error! {url} {e}") 38 | return self.result_domain 39 | 40 | if not response_body: 41 | return self.result_domain 42 | 43 | self.result_domain: Set[str] = match_subdomains(domain, response_body) 44 | 45 | # 提取IP地址 # 46 | ip_set: set = match_ip(response_body) 47 | if not ip_set: 48 | return self.result_domain 49 | 50 | for ip in ip_set: 51 | sleep(1) 52 | new_url = f"https://freeapi.robtex.com/pdns/reverse/{ip}" 53 | try: 54 | response_body: Optional[str] = self.send_request(url=new_url) 55 | except Exception as e: 56 | logger.error(f"{name} connect error! {new_url} {e}") 57 | break 58 | 59 | result_domain: Set[str] = match_subdomains(domain, response_body) 60 | self.result_domain = self.result_domain.union(result_domain) 61 | 62 | logger.info(f"{name}:{len(self.result_domain)} results found!") 63 | logger.debug(f"{name}:{self.result_domain}") 64 | return self.result_domain 65 | -------------------------------------------------------------------------------- /lib/modules/search/api_securitytrails.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding : utf-8-*- 3 | # coding:unicode_escape 4 | """ 5 | 作者:jammny 6 | 文件描述:收集securitytrails的子域名信息 7 | """ 8 | from lib.modules.search.api_base import ApiBase 9 | from lib.core.log import logger 10 | 11 | 12 | class Securitytrails(ApiBase): 13 | 14 | def __init__(self, domain: str) -> None: 15 | super().__init__() 16 | self.name = "Securitytrails" 17 | self.key: str = self.config['securitytrails_key'] 18 | self.domain: str = domain 19 | self.url: str = f"https://api.securitytrails.com/v1/domain/{domain}/subdomains?children_only=false" \ 20 | f"&include_inactive=true" 21 | self.headers: dict = { 22 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0', 23 | "accept": "application/json", 24 | "APIKEY": self.key 25 | } 26 | 27 | def parse_response(self, domain, response_json): 28 | """解析响应数据 29 | 30 | :param domain: 31 | :param response_json: 32 | :return: 33 | """ 34 | try: 35 | subdomains = response_json['subdomains'] 36 | return set([f"{i}.{domain}" for i in subdomains]) 37 | except: 38 | logger.error(f"Securitytrails parse response error. {response_json}") 39 | return set() 40 | -------------------------------------------------------------------------------- /lib/modules/search/api_virustotal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding : utf-8-*- 3 | # coding:unicode_escape 4 | """ 5 | 文件描述:收集virustotal的子域名信息 6 | """ 7 | from time import sleep 8 | from typing import Optional 9 | 10 | from lib.modules.search.api_base import ApiBase 11 | from lib.core.log import logger 12 | from lib.utils.tools import match_subdomains 13 | 14 | 15 | class Virustotal(ApiBase): 16 | 17 | def __init__(self, domain: str) -> None: 18 | super().__init__() 19 | self.domain: str = domain 20 | self.name: str = "Virustotal" 21 | self.result_domain: set = set() 22 | self.url: str = f"https://www.virustotal.com/ui/domains/{self.domain}/subdomains?relationships=resolutions" \ 23 | f"&cursor=&limit=10" 24 | self.headers: dict = { 25 | "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0", 26 | 'Content-Type': 'application/json', 27 | 'X-Tool': 'vt-ui-main', 28 | 'X-App-Version': 'v1x132x0', 29 | 'Accept-Ianguage': 'en-US,en;q=0.9,es;q=0.8', 30 | 'X-Vt-Anti-Abuse-Header': 'MTQ5NTc4NTM1OTItWkc5dWRDQmlaU0JsZG1scy0xNjY4MTQ5NzAxLjcyNw==', 31 | 'Te': 'trailers' 32 | } 33 | 34 | def parse_resqonse(self, response_json: dict) -> Optional[str]: 35 | """解析resqonse包 36 | 37 | :return: 38 | """ 39 | domain: str = self.domain 40 | # links中包含了本次请求的链接和下一条请求的链接 41 | links: dict = response_json['links'] 42 | # 如果存在下一条链接 43 | if links.__contains__('next'): 44 | result_domain = match_subdomains(domain, str(response_json)) 45 | self.result_domain = self.result_domain.union(result_domain) 46 | # 获取下一页的链接,每一页只能读取十条数据 47 | next_url = links['next'] 48 | return next_url 49 | return 50 | 51 | def get_domain(self) -> set: 52 | """获取子域名数据 53 | 54 | :return: 55 | """ 56 | name = self.name 57 | url = self.url 58 | logger.info(f"Running {name}...") 59 | 60 | try: 61 | response_json: Optional[dict] = self.send_request(url) 62 | except Exception as e: 63 | logger.error(f"[red]{name} connect error! {url} {e}[/red]") 64 | return self.result_domain 65 | 66 | next_url: Optional[str] = self.parse_resqonse(response_json) # 获取下一页的链接 67 | page = 10 # 每页仅返回10条数据,这里设置最大返回200条数据 68 | for i in range(page): 69 | try: 70 | response_json: Optional[dict] = self.send_request(next_url) 71 | except Exception as e: 72 | logger.error(f"[red]{name} connect error! {url} {e}[/red]") 73 | break 74 | next_url: Optional[str] = self.parse_resqonse(response_json) # 获取下一页的链接 75 | if not next_url: 76 | break 77 | sleep(1) 78 | 79 | logger.info(f"Virustotal:{len(self.result_domain)} results found!") 80 | return self.result_domain 81 | 82 | def run(self): 83 | pass 84 | -------------------------------------------------------------------------------- /lib/modules/search/api_zero.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:零零信安 API接口调用 6 | """ 7 | from typing import Set, Optional 8 | 9 | from lib.modules.search.api_base import ApiBase 10 | from lib.core.log import logger 11 | from lib.utils.tools import match_subdomains 12 | 13 | 14 | class Zero(ApiBase): 15 | def __init__(self, query: str, domain: str) -> None: 16 | super().__init__() 17 | self.name = "Zero" 18 | self.key = self.config['zero_key'] 19 | self.size: int = self.config['zero_size'] # 配置文件设定的最大检索值 20 | self.domain = domain # 根域名 21 | self.page_size: int = 40 # 每页返回的最大检索数 22 | self.url: str = "https://0.zone/api/data/" 23 | self.data = { 24 | "query": query, 25 | "query_type": "site", 26 | "page": 1, 27 | "pagesize": self.page_size, 28 | "zone_key_id": self.key 29 | } 30 | 31 | def get_domain(self) -> Set[str]: 32 | """域名收集专用 33 | 34 | :return: 35 | """ 36 | name = self.name 37 | domain: str = self.domain 38 | size: int = self.size 39 | page_size: int = self.page_size 40 | url = self.url 41 | logger.info(f"Running {name}...") 42 | 43 | # 先获取一页,看看检索出多少数据 # 44 | try: 45 | response_json: Optional[dict] = self.send_request(url, method="post") 46 | except Exception as e: 47 | logger.error(f"{name} connect error! {url} {e}") 48 | return self.result_domain 49 | 50 | if not response_json: 51 | return self.result_domain 52 | 53 | # 正则提取页面中域名 54 | self.result_domain: Set[str] = match_subdomains(domain, str(response_json)) 55 | 56 | # 根据搜索出的检索量和设定配置,循环遍历页数 # 57 | try: 58 | total: int = int(response_json['total']) 59 | page: int = self.get_page(total, size, page_size) 60 | self.circular_process(page, url, domain, method="post") 61 | except Exception as e: 62 | logger.error(f"{response_json} {e}") 63 | 64 | logger.info(f"{name}:{len(self.result_domain)} results found!") 65 | return self.result_domain 66 | 67 | def run(self): 68 | pass 69 | -------------------------------------------------------------------------------- /lib/modules/search/api_zoomeye.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:ZoomEye API接口调用 6 | """ 7 | from typing import Optional, Set 8 | 9 | from httpx import Client, Response 10 | 11 | from lib.modules.search.api_base import ApiBase 12 | from lib.core.log import logger 13 | from lib.utils.tools import match_subdomains 14 | 15 | 16 | class ZoomEye(ApiBase): 17 | def __init__(self, query: str, domain: str) -> None: 18 | super().__init__() 19 | self.name: str = "ZoomEye" 20 | self.query: str = query 21 | self.domain: str = domain 22 | self.page_size: int = 20 # 每页返回的最大检索数 23 | self.size: int = self.config['zoomeye_size'] # 配置文件设定的最大检索值 24 | 25 | def login(self) -> Optional[str]: 26 | """登陆获取用户token 27 | 28 | :return: 返回jwt信息 29 | """ 30 | username: str = self.config['zoomeye_mail'] 31 | password: str = self.config['zoomeye_pass'] 32 | name: str = self.name 33 | url: str = "https://api.zoomeye.org/user/login" 34 | try: 35 | with Client(verify=False) as c: 36 | response: Response = c.post(url=url, json={ 37 | 'username': username, 'password': password 38 | }) 39 | if response.status_code == 200: 40 | data: dict = response.json() 41 | access_token: str = data.get('access_token') 42 | logger.info(f"{name} login success!") 43 | return access_token 44 | else: 45 | logger.error(f"{name} login failed! {response} {response.text}") 46 | return 47 | except Exception as e: 48 | logger.error(f"{name} login failed! {url} {e}") 49 | return 50 | 51 | def get_domain(self) -> Set[str]: 52 | """域名收集调用 53 | 54 | :return: 返回包含域名的列表 55 | """ 56 | name = self.name 57 | domain: str = self.domain 58 | size: int = self.size 59 | page_size: int = self.page_size 60 | url: str = f"https://api.zoomeye.org/domain/search?q={domain}&type=1&page=[page]" # [page] 用于动态修改页数 61 | logger.info(f"Running {name}...") 62 | 63 | # 获取用户jwt口令, 判断是否登陆成功 # 64 | jwt: Optional[str] = self.login() 65 | if not jwt: 66 | return self.result_domain 67 | else: 68 | self.headers['Authorization'] = f"JWT {jwt}" 69 | 70 | # 先获取一页,看看检索出多少数据 # 71 | new_url: str = url.replace("[page]", "1") 72 | try: 73 | response_json: Optional[dict] = self.send_request(new_url) 74 | except Exception as e: 75 | logger.error(f"{name} connect error! {url} {e}") 76 | return self.result_domain 77 | 78 | if not response_json: 79 | return self.result_domain 80 | 81 | # 正则提取页面中域名 82 | self.result_domain: Set['str'] = match_subdomains(domain, str(response_json)) 83 | 84 | # 根据搜索出的检索量和设定配置,循环遍历页数 # 85 | try: 86 | total: int = response_json['total'] 87 | page: int = self.get_page(total, size, page_size) 88 | self.circular_process(page, url, domain) 89 | except Exception as e: 90 | logger.error(f"{response_json} {e}") 91 | 92 | logger.info(f"{name}:{len(self.result_domain)} results found!") 93 | logger.debug(f"{name}:{self.result_domain}") 94 | return self.result_domain 95 | 96 | def run(self): 97 | pass 98 | -------------------------------------------------------------------------------- /lib/modules/sub/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding : utf-8-*- 3 | # coding:unicode_escape 4 | """ 5 | 作者:jammny 6 | 文件描述: 7 | """ 8 | -------------------------------------------------------------------------------- /lib/modules/sub/bruteforc/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding : utf-8-*- 3 | # coding:unicode_escape 4 | """ 5 | 作者:jammny 6 | 文件描述: 7 | """ 8 | -------------------------------------------------------------------------------- /lib/modules/sub/bruteforc/thirdparty.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 6 | """ 7 | import subprocess 8 | from typing import Set 9 | 10 | from rich.progress import Progress 11 | 12 | from lib.core.settings import THIRDPARTY_APP 13 | 14 | 15 | def ksubdomain(dic, domain) -> Set[str]: 16 | """ 17 | 18 | :param dic: 19 | :param domain: 20 | :return: 21 | """ 22 | app = THIRDPARTY_APP['ksubdomain'] 23 | command = f"{app} e -d {domain} -f {dic} -skip-wild --silent --only-domain" 24 | 25 | with Progress(transient=True) as progress: 26 | task = progress.add_task("[red]Brute...", start=False, total=None) 27 | status, result = subprocess.getstatusoutput(command) 28 | progress.update(task, advance=100) 29 | 30 | if status != 0: 31 | return set() 32 | 33 | subdomain: set = set(result.split("\n")) 34 | subdomain.discard('') 35 | return subdomain 36 | -------------------------------------------------------------------------------- /lib/modules/sub/custom.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:解析自定义DNS数据源的核心模块。 6 | """ 7 | import ast 8 | import time 9 | from typing import Union, Set, Optional 10 | 11 | from httpx import Client 12 | 13 | from lib.core.log import logger 14 | from lib.utils.tools import match_subdomains 15 | 16 | 17 | class Custom(object): 18 | def __init__(self, domain: str, datasets: dict) -> None: 19 | """参数初始化 20 | 21 | :param domain: 目标域名 22 | :param datasets: YAML解析规则内容 23 | """ 24 | self.domain: str = domain 25 | self.datasets: dict = datasets 26 | 27 | self.result_domain: set = set() # 使用集合存储结果,数据免去重 28 | 29 | self.id: str = self.datasets['id'] 30 | self.rule: dict = self.datasets['rule'] 31 | 32 | # 是否循环页数 33 | if self.rule['while']: 34 | self.page = self.rule['start_page'] 35 | self.add_num = self.rule['add_num'] 36 | # 请求参数 37 | self.request: dict = self.rule['request'] 38 | self.header: str = self.request['header'] 39 | self.method: str = self.request['method'] 40 | self.timeout: int = self.request['timeout'] 41 | self.url: str = self.request['url'].replace('{domain}', self.domain) 42 | # 响应参数 43 | self.response: dict = self.rule['response'] 44 | self.code: str = self.response['code'] 45 | 46 | def circular_process(self) -> None: 47 | """循环处理 那些有页数增加的配置 48 | 49 | :return: 50 | """ 51 | url = self.url 52 | while True: 53 | logger.debug(f"{self.id} current page: {self.page}") 54 | self.url: str = url.replace('{page}', str(self.page)) 55 | self.page += self.add_num 56 | response_text: Union[str, None] = self.send_request() 57 | if response_text: 58 | # 解析请求 59 | res: set = match_subdomains(self.domain, response_text) 60 | # 删除影响元素 61 | res.discard(self.domain) 62 | if res: 63 | self.result_domain = self.result_domain.union(res) 64 | else: 65 | break 66 | else: 67 | break 68 | time.sleep(2) 69 | return 70 | 71 | def send_request(self) -> Optional[str]: 72 | """发送接口请求 73 | 74 | :return: 75 | """ 76 | try: 77 | with Client(verify=False, timeout=self.timeout, headers=self.header, follow_redirects=True) as c: 78 | # 判断使用什么请求方法 79 | if self.method == 'get': 80 | response = c.get(self.url) 81 | elif self.method == 'post': 82 | # 携带请求参数 83 | tmp: str = self.request['data'] 84 | if tmp.__contains__('{domain}'): 85 | tmp: str = tmp.replace('{domain}', self.domain) 86 | data: dict = ast.literal_eval(tmp) 87 | response = c.post(self.url, data=data) 88 | else: 89 | logger.error(f"[red]{self.id}.yaml The wrong request method was configured![/red]") # 配置了错误的方法 90 | except Exception as e: 91 | logger.error(f'[red]{self.id} {e}[/red]') 92 | return 93 | # 判断响应码是否一致 94 | if response.status_code == self.code: 95 | return response.text 96 | else: 97 | logger.error(f"[red]{self.id}, response code error: {response.status_code}[/red]") 98 | # logger.debug(response.text) 99 | return 100 | 101 | def run(self) -> Set['str']: 102 | """类执行入口 103 | 104 | :return: 105 | """ 106 | logger.info(f"Running {self.id} modules...") 107 | if self.rule['while']: 108 | self.circular_process() 109 | else: 110 | response_text: Union[str, None] = self.send_request() 111 | if response_text: 112 | self.result_domain: Set['str'] = match_subdomains(self.domain, response_text) # 正则提取页面中域名 113 | self.result_domain.discard(self.domain) # 删除元组中的根域名 114 | 115 | logger.info(f"{self.id}: {len(self.result_domain)} results found!") 116 | return self.result_domain 117 | -------------------------------------------------------------------------------- /lib/modules/sub/search.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 加载内置模块的函数 6 | """ 7 | from lib.modules.search.api_binaryedge import Binaryedge 8 | from lib.modules.search.api_censys import Censys 9 | from lib.modules.search.api_fofa import Fofa 10 | from lib.modules.search.api_fullhunt import Fullhunt 11 | from lib.modules.search.api_hunter import Hunter 12 | from lib.modules.search.api_quake import Quake 13 | from lib.modules.search.api_securitytrails import Securitytrails 14 | from lib.modules.search.api_zero import Zero 15 | from lib.modules.search.api_zoomeye import ZoomEye 16 | from lib.modules.search.api_dnsdumpster import Dnsdumpster 17 | from lib.modules.search.api_robtex import Robtex 18 | from lib.modules.search.api_virustotal import Virustotal 19 | from lib.modules.sub.vulnerability.dns_zone_transfer import AXFR 20 | 21 | 22 | def dns_zone_transfer_(domain: str) -> set: 23 | """域传输漏洞检测""" 24 | return AXFR(domain).run() 25 | 26 | 27 | def dnsdumpster_(domain: str) -> set: 28 | return Dnsdumpster(domain).get_domain() 29 | 30 | 31 | def robtex_(domain: str) -> set: 32 | return Robtex(domain).get_domain() 33 | 34 | 35 | def virustotal_(domain: str) -> set: 36 | return Virustotal(domain).get_domain() 37 | 38 | 39 | def fofa_(domain: str) -> set: 40 | query: str = f'domain="{domain}"&&(protocol="http"||protocol="https")' 41 | return Fofa(query, domain).get_domain() 42 | 43 | 44 | def zoomeye_(domain: str) -> set: 45 | return ZoomEye(query=f"{domain}", domain=domain).get_domain() 46 | 47 | 48 | def hunter_(domain: str) -> set: 49 | return Hunter(query=f'domain.suffix="{domain}"&&(protocol="http"||protocol="https")', domain=domain).get_domain() 50 | 51 | 52 | def binaryedge_(domain: str) -> set: 53 | return Binaryedge(domain).get_domain() 54 | 55 | 56 | def securitytrails_(domain: str) -> set: 57 | return Securitytrails(domain).get_domain() 58 | 59 | 60 | def fullhunt_(domain: str) -> set: 61 | return Fullhunt(domain).get_domain() 62 | 63 | 64 | def censys_(domain: str) -> set: 65 | return Censys(domain).get_domain() 66 | 67 | 68 | def quake_(domain: str) -> set: 69 | return Quake(query=f"domain: {domain} AND service: http", domain=domain).get_domain() 70 | 71 | 72 | def zero_(domain: str) -> set: 73 | return Zero(query=f'url=={domain}&&(service=http||service=https)', domain=domain).get_domain() -------------------------------------------------------------------------------- /lib/modules/sub/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 工具函数。 6 | """ 7 | import os 8 | from typing import List 9 | 10 | import yaml 11 | 12 | from lib.modules.cdn.dns_resolver import DnsResolver 13 | 14 | 15 | def get_subname(subdomain: str): 16 | """获取子域名中 子域的数据 17 | 18 | :param subdomain: 子域名数据:test.baidu.com 19 | :return: 20 | """ 21 | # 直接通过 . 进行分割 22 | return subdomain.split('.')[0] 23 | 24 | 25 | def get_dir_yaml(file_path: str) -> list: 26 | """从目录中获取自定义DNS数据集的文件内容 27 | 28 | :return: 返回文件内容 29 | """ 30 | # 遍历目录中的文件名 31 | yaml_files: list = list() 32 | for root, dirs, files in os.walk(file_path): 33 | for file in files: 34 | yaml_files.append(os.path.join(root, file)) # 将文件名添加到列表 35 | # 遍历文件内容 36 | context: list = list() 37 | for i in yaml_files: 38 | with open(i, mode='r', encoding='utf-8') as f: 39 | data: dict = yaml.safe_load(f.read()) 40 | context.append(data) 41 | return context 42 | 43 | 44 | def generic_parsing(domain: str) -> List[str]: 45 | """检测域名泛解析 46 | 47 | :param domain: 目标域名 48 | :return: 49 | """ 50 | test_domain: str = f"fuckfucktest.{domain}" 51 | # 如果能够成功解析出IP,说明存在泛解析 52 | dns_results: List[tuple] = DnsResolver().run(targets_list=[test_domain],) 53 | if dns_results: 54 | ip: List[str] = dns_results[0][1] 55 | return ip 56 | return list() 57 | -------------------------------------------------------------------------------- /lib/modules/sub/vulnerability/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 作者:jammny 5 | 文件描述: 6 | """ 7 | -------------------------------------------------------------------------------- /lib/modules/sub/vulnerability/dns_zone_transfer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 域传输漏洞检测 6 | """ 7 | from dataclasses import dataclass 8 | from typing import Any 9 | 10 | from dns import resolver, zone, query 11 | 12 | from lib.core.log import logger 13 | 14 | 15 | @dataclass() 16 | class AXFR(object): 17 | domain: str 18 | results = set() 19 | 20 | def ns(self) -> None: 21 | """对域名进行ns记录查询""" 22 | try: 23 | answers: resolver.Answer = resolver.resolve(self.domain, "NS") 24 | except: 25 | logger.error(f'No NS record found for {self.domain}') 26 | return 27 | nsservers: list = [str(answer) for answer in answers] 28 | if not nsservers: 29 | return 30 | for server in nsservers: 31 | self.axfr(server) 32 | 33 | def axfr(self, server: str) -> None: 34 | logger.debug(f'Trying to perform domain transfer in {server} of {self.domain}') 35 | try: 36 | xfr: Any = query.xfr(where=server, zone=self.domain, timeout=5, lifetime=10) 37 | zone.from_xfr(xfr) 38 | except: 39 | # logger.debug(f'Domain transfer to server {server} of {self.domain} failed') 40 | return 41 | names: list = zone.nodes.keys() 42 | for name in names: 43 | full_domain: str = str(name) + '.' + self.domain 44 | # print(full_domain) 45 | self.results.add(full_domain) 46 | 47 | def run(self) -> set: 48 | logger.info(f"Running dns zone transfer...") 49 | self.ns() 50 | if self.results: 51 | # print(f'Found the domain transfer record of {self.domain} on {server}') 52 | logger.info(f"[+] Dns zone transmission vulnerability exists!") 53 | logger.info(f"Dns zone transfer: {len(self.results)} results found!") 54 | # print(f"Dns zone transfer: {self.results}") 55 | return self.results 56 | 57 | 58 | if __name__ == '__main__': 59 | AXFR('vulhub.org').run() 60 | -------------------------------------------------------------------------------- /lib/utils/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding : utf-8-*- 3 | # coding:unicode_escape 4 | """ 5 | 作者:jammny 6 | 文件描述: 7 | """ 8 | -------------------------------------------------------------------------------- /lib/utils/encrypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 作者:jammny 5 | 文件描述: 数据加密模块 6 | """ 7 | import random 8 | import string 9 | 10 | 11 | class GetKey: 12 | def random_key(self, length): 13 | """ 14 | 获取随机 15 | :param length: 16 | :return: 17 | """ 18 | numOfNum = random.randint(1, length - 1) 19 | numOfLetter = length - numOfNum 20 | slcNum = [random.choice(string.digits) for i in range(numOfNum)] 21 | slcLetter = [random.choice(string.ascii_letters) for i in range(numOfLetter)] 22 | slcChar = slcNum + slcLetter 23 | random.shuffle(slcChar) 24 | getPwd = ''.join([i for i in slcChar]) 25 | return getPwd 26 | -------------------------------------------------------------------------------- /lib/utils/file.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Union 3 | 4 | 5 | def read_json_file(path: str) -> Union[list, dict, None]: 6 | """读json文件内容 7 | 8 | :param path: 文件路径 9 | :return: 10 | """ 11 | try: 12 | with open(path, mode='r', encoding="utf-8") as f: 13 | json_data: Union[list, dict] = json.load(f) 14 | return json_data 15 | except Exception as e: 16 | return 17 | 18 | 19 | def write_txt(path: str, data: list) -> bool: 20 | """写入目标文件 21 | 22 | :param path: 文件路径 23 | :param data: 写入数据 24 | :return: 25 | """ 26 | try: 27 | with open(path, mode="w", encoding="utf-8") as f: 28 | f.write("\n".join(data)) 29 | return True 30 | except: 31 | return False 32 | -------------------------------------------------------------------------------- /lib/utils/getfiles.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 获取文件 6 | """ 7 | import yaml 8 | import os 9 | 10 | 11 | def get_yaml(file_path: str) -> list: 12 | """从目录中获取自定义DNS数据集的文件内容 13 | 14 | :return: 返回文件内容 15 | """ 16 | # 遍历目录中的文件名 17 | yaml_files: list = list() 18 | for root, dirs, files in os.walk(file_path): 19 | for file in files: 20 | yaml_files.append(os.path.join(root, file)) # 将文件名添加到列表 21 | # 遍历文件内容 22 | context: list = list() 23 | for i in yaml_files: 24 | with open(i, mode='r', encoding='utf-8') as f: 25 | data: dict = yaml.safe_load(f.read()) 26 | context.append(data) 27 | return context 28 | 29 | 30 | if __name__ == '__main__': 31 | pass 32 | -------------------------------------------------------------------------------- /lib/utils/mail.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 邮件发送模块。 6 | """ 7 | from dataclasses import dataclass 8 | import smtplib 9 | from email.mime.text import MIMEText 10 | from email.header import Header 11 | from email.mime.multipart import MIMEMultipart 12 | from email.mime.application import MIMEApplication 13 | from email.utils import formataddr 14 | 15 | from lib.core.log import logger 16 | from lib.core.settings import SEND_EMAIL, SEND_PASS, REC_EMAIL, SMTP_SERVER, SMTP_PORT 17 | 18 | 19 | @dataclass() 20 | class SendEmail: 21 | mail_header: str # 邮件的主题 22 | 23 | my_sender = SEND_EMAIL # 发件人邮箱账号 24 | my_pass = SEND_PASS # 发件人邮箱密码 25 | my_user = REC_EMAIL # 收件人邮箱账号 26 | smtp_server = SMTP_SERVER 27 | smtp_port = SMTP_PORT 28 | 29 | def send_file(self, mail_msg: str, file_name: str, report_name: str): 30 | try: 31 | if ',' in self.my_user: 32 | receivers = self.my_user.split(',') 33 | else: 34 | receivers = [self.my_user] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱 35 | msg = MIMEMultipart() # 设置电子邮件消息 36 | msg['Subject'] = self.mail_header # 邮件的主题 37 | msg['From'] = formataddr(("JWS", self.my_sender)) 38 | msg['To'] = formataddr(("", self.my_user)) 39 | msg.attach(MIMEText(f"{mail_msg}", 'plain')) 40 | 41 | # 压缩文件 42 | with open(file_name, 'rb') as f: 43 | attach = MIMEApplication(f.read(), _subtype='zip') 44 | attach.add_header('Content-Disposition', 'attachment', filename=report_name) 45 | msg.attach(attach) 46 | 47 | server = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port) # 发件人邮箱中的SMTP服务器 48 | server.login(self.my_sender, self.my_pass) # 括号中对应的是发件人邮箱账号、邮箱密码 49 | server.sendmail(self.my_sender, receivers, msg.as_string()) # 括号中对应的是发件人邮箱账号、收件人邮箱账号、发送邮件 50 | server.quit() # 关闭连接 51 | logger.info("[g]Email sent successfully![/g]") 52 | except Exception as e: 53 | logger.error(f"[red]Email sending failed! {e} [/red]") 54 | 55 | def send_msg(self, mail_msg: str): 56 | try: 57 | if ',' in self.my_user: 58 | receivers = self.my_user.split(',') 59 | else: 60 | receivers = [self.my_user] # 接收邮件,可设置为你的QQ邮箱或者其他邮箱 61 | message = MIMEText(mail_msg, 'plain', 'utf-8') 62 | message['From'] = Header("JWS", 'utf-8') 63 | message['To'] = Header("测试", 'utf-8') 64 | message['Subject'] = Header(self.mail_header, 'utf-8') 65 | smtpObj = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port) 66 | smtpObj.login(self.my_sender, self.my_pass) 67 | smtpObj.sendmail(self.my_sender, receivers, message.as_string()) 68 | logger.info("Email sent successfully!") 69 | except Exception as e: 70 | logger.error(f"[red]Email sending failed! {e}[/red]") 71 | -------------------------------------------------------------------------------- /lib/utils/thread.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述: 文件描述:线程池封装。 6 | """ 7 | from queue import Queue 8 | from typing import Callable 9 | from concurrent.futures import ThreadPoolExecutor 10 | 11 | 12 | def threadpool_task(task: Callable, queue_data: list, thread_count: int = 100, task_args: tuple = ()): 13 | """ 14 | 线程池模板 15 | :param task: 需要执行的多线程任务 16 | :param queue_data: 需要添加到队列的数据 17 | :param thread_count: 并发数 18 | :param task_args: 除了Queue之外的的参数 19 | :return 20 | """ 21 | queue_obj: Queue = Queue(maxsize=0) 22 | for i in queue_data: 23 | queue_obj.put(i) 24 | args: tuple = task_args + (queue_obj,) 25 | with ThreadPoolExecutor(max_workers=thread_count) as pool: 26 | for _ in range(queue_obj.qsize()): 27 | futrue = pool.submit(lambda p: task(*p), args) 28 | # futrue.add_done_callback(done) # 回调函数 29 | -------------------------------------------------------------------------------- /lib/utils/tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | """ 4 | 前言:切勿将本工具和技术用于网络犯罪,三思而后行! 5 | 文件描述:此模块专门用于处理数据。 6 | """ 7 | import re 8 | import time 9 | from typing import Union, Optional, Set 10 | 11 | from IPy import IP 12 | 13 | from lib.core.settings import CIDR_CONFIG 14 | 15 | 16 | def match_ip(string: str) -> Set[str]: 17 | """从字符串中提取IP 18 | 19 | :param string: 需要处理的字符串 20 | :return: 21 | """ 22 | ip_pattern = r'\b(?:\d{1,3}\.){3}\d{1,3}\b' # 此正则匹配标准IPv4地址 23 | return set(re.findall(ip_pattern, string)) 24 | 25 | 26 | def domain_format(data: str) -> Union[str, None]: 27 | """提取字符串中的域名数据 28 | 29 | :param data: str 30 | :return: str 31 | """ 32 | # 正则处理 33 | res = re.search('((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}', data) 34 | if res: 35 | return res.group() 36 | else: 37 | return 38 | 39 | def match_subdomains(domain: str, html: str, distinct: bool = True, fuzzy: bool = True) -> Union[list, set]: 40 | """正则匹配域名 41 | 42 | :param domain: 目标域名 43 | :param html: 需要提取域名的页面 44 | :param distinct: 是否返回集合 45 | :param fuzzy: 是否开启fuzz匹配 46 | :return: dict | set 47 | """ 48 | if fuzzy: 49 | regexp = r'(?:[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?\.){0,}' + domain.replace('.', r'\.') 50 | result = re.findall(regexp, html, re.I) 51 | if not result: 52 | return set() 53 | deal = map(lambda s: s.lower(), result) 54 | if distinct: 55 | return set(deal) 56 | else: 57 | return list(deal) 58 | else: 59 | regexp = r'(?:\>|\"|\'|\=|\,)(?:http\:\/\/|https\:\/\/)?' \ 60 | r'(?:[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?\.){0,}' \ 61 | + domain.replace('.', r'\.') 62 | result = re.findall(regexp, html, re.I) 63 | if not result: 64 | return set() 65 | regexp = r'(?:http://|https://)' 66 | deal = map(lambda s: re.sub(regexp, '', s[1:].lower()), result) 67 | if distinct: 68 | return set(deal) 69 | else: 70 | return list(deal) 71 | 72 | 73 | def runtime_format(start_time: float, end_time: float) -> str: 74 | """计算程序运行时长,格式化输出结果 75 | 76 | :param start_time: 程序开始时间 77 | :param end_time: 程序结束时间 78 | :return: 程序运行时长 79 | """ 80 | run_time: float = end_time - start_time 81 | seconds: int = int(run_time) # 秒 82 | # milliseconds = int((run_time - seconds) * 1000) # 毫秒 83 | if seconds > 60: 84 | mintues: int = seconds // 60 85 | new_seconds: int = seconds % 60 86 | formatted_time = f"{mintues}min {new_seconds}s" 87 | else: 88 | formatted_time = f"{seconds}s" 89 | return formatted_time 90 | 91 | 92 | 93 | 94 | 95 | def blacklist_cidr(ip): 96 | """IP地址 黑名单过滤 97 | 98 | :return: 99 | """ 100 | # 将IP转成cidr的格式 101 | ip_mask = IP(f"{ip}/255.255.255.0", make_net=True) 102 | cidr = str(ip_mask) 103 | black_list = [ 104 | '112.90.80.0/24' 105 | ] 106 | return all(i not in cidr for i in black_list) 107 | 108 | 109 | 110 | def get_time(): 111 | """获取当前时间,将时间戳转成:年/月/日 时/分/秒 112 | 113 | :return: 当前时间 114 | """ 115 | time_format = "%Y%m%d%H%M%S" 116 | time_now = time.localtime(int(round(time.time()*1000))/1000) 117 | return time.strftime(time_format, time_now) 118 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | <h2 align="center">JWS-CLI</h2> 3 | 4 | > 前言:信息收集是渗透测试中最重要的一环,也是最繁琐和机械化的一环。jws-cli 是一款基于python的可拓展、可定制化的一键信息收集工具,适用于辅助测试人员在攻防演练和SRC项目场景下进行快速信息收集和资产梳理。 5 | 6 | ### 工具特点预览 7 | 1. 对目标资产实现一键收集:子域名收集+CDN识别+端口扫描+WEB指纹识别+C段扫描+WAF识别。 8 | 2. 支持把企业全称(如:XX有限公司)作为收集目标,程序将自动化收集关于目标企业的相关资产。 9 | 3. 支持通过编写配置文件来拓展DNS数据集,以增加子域名收集模块的灵活性。 10 | 4. 支持调用你喜欢的第三方工具来代替程序本身的功能模块,如:使用ksubdomain代替自身的爆破模块。 11 | 5. 不仅仅是信息收集,支持调用第三方工具对收集的资产进行漏洞扫描,如afrog。 12 | 6. 支持生成可视化收集结果页面,并推送到用户邮箱中。 13 | 14 | ### 一键自动化扫描 15 | 1. 下载项目:`git clone https://github.com/jammny/jws-cli.git` 16 | 2. 安装依赖:`pip install -r requirements.txt` 17 | 3. 一键自动化扫描:`python jws-cli.py -t example.com --auto` 18 | 4. 一键自动化批量扫描:`python jws-cli.py -f targets.txt --auto` 19 | 5. 一键自动化收集目标企业资产:`python jws-cli.py -c "xx有限公司" --auto` 20 | 6. 使用帮助:`python jws-cli.py --help` 21 | 22 | 23 | ### 配置文件 24 | 程序配置文件路径: `jws-cli/db/config.yaml` 25 | ```yaml 26 | # 开启/关闭 调试模式 27 | debug_mode: False 28 | 29 | # 开启/关闭 数据表格展示 30 | show_table: True 31 | 32 | 33 | # 配置程序需要调用的api接口信息 # 34 | api_key: 35 | # 零零信安 https://0.zone/ 36 | zero_key: "" 37 | zero_size: 200 38 | 39 | # quake https://quake.360.net/quake/#/index 40 | quake_key: "" 41 | quake_size: 200 42 | 43 | # zoomeye https://www.zoomeye.org/ 44 | zoomeye_mail: "" # zoomeye账号 45 | zoomeye_pass: "" # zoomeye密码 46 | zoomeye_size: 200 # 最大检索量 47 | 48 | # hunter https://hunter.qianxin.com/ 49 | hunter_key: "" 50 | hunter_size: 200 # 最大检索量 51 | 52 | # fofa https://fofa.info/ 53 | fofa_email: "" 54 | fofa_key: "" 55 | fofa_size: 200 # 最大检索量 56 | 57 | # securitytrails https://securitytrails.com/ 58 | securitytrails_key: "" 59 | 60 | # fullhunt https://fullhunt.io/search 61 | fullhunt_key: "" 62 | 63 | # binaryedge https://binaryedge.io/ 64 | binaryedge_key: "" 65 | 66 | # censys https://search.censys.io/ 67 | censys_id: "" 68 | censys_secret: "" 69 | 70 | 71 | # 自动化扫描配置 # 72 | # 默认情况下,程序自动进行子域名收集和存活资产探测任务。你也可以根据自己的需求,自由搭配需要开启的扫描模块。 73 | auto_setting: 74 | port_scan: True # 开启/关闭 主机端口扫描。 75 | cidr_scan: True # 开启/关闭 C段资产扫描。 76 | poc_scan: True # 开启/关闭 POC漏洞扫描。 77 | 78 | # 开启/关闭 智能模式。 79 | # 智能模式下,会减少开销。仅对没有waf的url进行POC扫描。 80 | smart_mode: True 81 | 82 | # 支持通过定制化黑名单列表排除没有意义的C段资产,仅当 cidr_scan = True 时有效,列表中的值对应IP解析后的地址信息 83 | filter_blacklist: [ 84 | '微软', '阿里云', '阿里云BGP节点', '阿里云BGP服务器', '阿里巴巴', 'Microsoft', 'CDN', 'Azure', '华为', '华为云', 85 | '腾讯云', '网宿', 'Amazon', '运营商','世纪互联BGP数据中心', '内部网', '局域网', '对方和您在同一内部网', '亚马逊', '127.0.0.1' 86 | ] 87 | 88 | # 开启/关闭 生成扫描报告 89 | generate_report: True 90 | 91 | # 邮箱信息配置 92 | smtp_server: smtp.163.com # smtp 邮箱服务器 93 | smtp_port: 465 # smtp 端口号 94 | send_email: "" # 发件人邮箱账号 95 | send_pass: "" # 发件人邮箱授权码 96 | rec_email: "" # 收件人邮箱, 如果有多个收件人需要使用英文逗号隔开 97 | 98 | 99 | # 子域名扫描配置 # 100 | # 默认情况下,程序自动进行被动子域名信息收集,支持使用域名置换技术生成fuzz字典,支持额外调用ksubdomain来完成域名遍历任务。 101 | sub_scan: 102 | # 爆破模式参数。 103 | brute_engine: "ksubdomain" # 可选参数:system 和 ksubdomain。 104 | brute_fuzzy: False # 开启/关闭 域名置换技术。 105 | 106 | 107 | # 端口扫描配置 # 108 | # 默认情况下,程序会对主机进行存活探测,并对存活的端口进行指纹识别。支持调用nimscan完成端口扫描任务,支持自定义要扫描的端口范围。 109 | port_scan: 110 | engine: "nimscan" # 可选参数:system 和 nimscan。 111 | banner_status: True # 开启/关闭 指纹识别。 112 | port_range: '21,22,23,80-99,135,139,442-445,666,800,801,808,880,888,889,1000-2379,3000-10010,11115,12018,12443,14000,16080,18000-18098,19001,19080,20000,20720,21000,21501,21502,28018,20880,27017' 113 | 114 | 115 | # C段扫描配置 # 116 | # 支持统计目标C段中,资产IP出现的次数;支持使用 occurrence_limit 参数跳过不符合条件的C段。 117 | cidr_scan: 118 | engine: "system" # 可选参数:system 和 fofa 119 | occurrence_limit: 3 # 如果相同C段统计IP出现次数,次数>=3才扫描 120 | 121 | 122 | # POC扫描配置 # 123 | # 支持调用afrog完成扫描任务。 124 | poc_scan: 125 | engine: "afrog" # 可选参数:afrog 126 | ``` 127 | 128 | 129 | ### 更新日志 130 | 131 | | 更新时间(版本) | 更新内容 | 备注 | 132 | |-------------------|------------------|------| 133 | | 2023.9.14(v0.2.1) | 已知bug修复。 |有问题联系我| 134 | | 2023.9.11(v0.2.0) | 重写了一些模块,新增了一些功能。 |有问题联系我| 135 | | 2023.5.29(v0.1.0) | 优化端口泛滥、域名泛滥处理逻辑。 |有问题联系我| 136 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | rich~=13.5.2 2 | typer~=0.9.0 3 | pyyaml~=6.0 4 | pymmh3~=0.0.5 5 | httpx~=0.24.0 6 | parsel==1.8.1 7 | ping3~=4.0.4 8 | tinydb~=4.7.1 9 | IPy~=1.1 10 | scapy~=2.5 11 | jinja2~=3.1.2 12 | qqwry-py3~=1.2.1 13 | dnspython~=2.3.0 14 | pluginbase~=1.0.1 15 | beautifulsoup4 16 | aiohttp 17 | requests 18 | tenacity 19 | DingtalkChatbot~=1.5.7 20 | requests_raw 21 | -------------------------------------------------------------------------------- /thirdparty/afrog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jammny/jws-cli/b454bd0cdae2fae7a086485375dba8b419d6ad05/thirdparty/afrog -------------------------------------------------------------------------------- /thirdparty/ksubdomain: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jammny/jws-cli/b454bd0cdae2fae7a086485375dba8b419d6ad05/thirdparty/ksubdomain -------------------------------------------------------------------------------- /thirdparty/nimscan: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jammny/jws-cli/b454bd0cdae2fae7a086485375dba8b419d6ad05/thirdparty/nimscan --------------------------------------------------------------------------------