├── links.txt ├── screenshot.png ├── requirements.txt ├── models.txt ├── prompts ├── shortname.txt └── files.txt ├── README.md ├── benchmark_report.md ├── benchmark.py ├── fuzzer_shortname.py ├── fuzzer.py └── benchmark_report.html /links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Invicti-Security/brainstorm/HEAD/screenshot.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.31.0 2 | beautifulsoup4>=4.12.0 3 | colorama>=0.4.6 4 | jinja2>=3.1.2 5 | -------------------------------------------------------------------------------- /models.txt: -------------------------------------------------------------------------------- 1 | mistral:latest 2 | llama3.1:latest 3 | llama3.2:latest 4 | qwen2.5:latest 5 | qwen2.5-coder:latest 6 | qwen2.5-coder:14b 7 | gemma:latest 8 | phi3:latest -------------------------------------------------------------------------------- /prompts/shortname.txt: -------------------------------------------------------------------------------- 1 | You are tasked with guessing the full name of a file based on its 8.3 filename (also called a short filename or SFN). This filename convention was used by old versions of DOS and versions of Microsoft Windows. 2 | 3 | An 8.3 filename consists of up to 8 characters for the filename, followed by a period and up to 3 characters for the file extension. If the original filename was longer than 8 characters, it was typically truncated and a tilde (~) followed by a number was added. 4 | 5 | Here is the 8.3 filename you need to expand: 6 | 7 | 8 | {{INPUT_83_FILENAME}} 9 | 10 | 11 | To generate potential full filenames, follow these guidelines: 12 | 1. The part before the tilde (if present) is likely the beginning of the original filename. 13 | 2. The number after the tilde indicates which truncated version it is (1 being the first). 14 | 3. The file extension (after the period) is likely correct and complete. 15 | 4. Consider common words, abbreviations, or phrases that might complete the truncated filename. 16 | 5. Take into account the context of common file types and naming conventions. 17 | 6. Consider different naming conventions such as camel case, snake case, and kebab case. 18 | 7. All the filenames should start with the filename before the tilde and use the same extension. DO NOT generate filenames that don't start with the filename before the tilde or use a different extension. 19 | 20 | Here's an example of how to generate new potential full filenames based on a 8.3 filename: 21 | 22 | Example: 23 | 24 | USERRE~1.JSP 25 | 26 | 27 | 28 | userRegister.jsp 29 | userRegister1.jsp 30 | userRegistration.jsp 31 | userRegistration1.jsp 32 | 33 | 34 | Your task is to provide 10 potential full names based on the given 8.3 filename. Be creative and consider various possibilities, but ensure they are plausible and relevant. 35 | 36 | Please specify your list of 10 potential full filenames inside tags, with each filename on a new line. -------------------------------------------------------------------------------- /prompts/files.txt: -------------------------------------------------------------------------------- 1 | You are an AI assistant tasked with brainstorming new potential filenames and directories that could be present on a website based on an existing list. 2 | Your goal is to generate a list of 30 new filenames or directories that could potentially exist on this website. 3 | 4 | Here is the list of known filenames and directories: 5 | 6 | 7 | {{initialLinks}} 8 | 9 | 10 | Here are the server headers: 11 | 12 | 13 | {{serverHeaders}} 14 | 15 | 16 | When brainstorming new filenames and directories, follow these guidelines: 17 | 18 | - Consider common web technologies, frameworks, and practices. 19 | - Analyze the server_headers section and use this information when generating new filenames. 20 | - Observe file extensions (e.g., .php, .asp, .aspx, .jsp) used in the existing list and use them consistently when generating new filenames. 21 | - Think about potential API endpoints or web services. 22 | - Maintain consistency with the existing file structure and naming conventions. 23 | - Don't generate empty files/directories. 24 | - Don't generate static or text files (e.g. extensions .txt, .css, .js) 25 | - Don't generate .php files unless there are .php files in the list from 26 | 27 | Here are some examples of how to generate new filenames and directories based on an existing list: 28 | 29 | Example 1: 30 | 31 | index.php 32 | users.php 33 | logs/view.php 34 | 35 | 36 | 37 | login.php 38 | register.php 39 | resetPassword.php 40 | auth.php 41 | logs/index.php 42 | logs/list.php 43 | logs/delete.php 44 | 45 | 46 | Example 2: 47 | 48 | default.aspx 49 | forgot_password.aspx 50 | files/upload.aspx 51 | 52 | 53 | 54 | register.aspx 55 | login.aspx 56 | files/list.aspx 57 | files/delete.aspx 58 | 59 | 60 | Example 3: 61 | 62 | api/v2/list 63 | api/v2/new 64 | 65 | 66 | 67 | api/v2/register 68 | api/v2/delete 69 | api/v2/search 70 | 71 | 72 | Example 4: 73 | 74 | admin/login 75 | admin/forgotPassword 76 | 77 | 78 | 79 | admin/register 80 | admin/users 81 | admin/dashboard 82 | 83 | 84 | Now, based on the provided list of existing files and directories, generate a list of 30 new potential filenames or directories. 85 | Be creative but realistic in your suggestions. Output your list inside tags, with each item on a new line. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # brainstorm 2 | 3 | A smarter web fuzzing tool that combines local LLM models (via Ollama) and [ffuf](https://github.com/ffuf/ffuf) to optimize directory and file discovery. 4 | 5 | I wrote a blog post about the ideas behind this tool: 6 | [Brainstorm tool release: Optimizing web fuzzing with local LLMs](https://www.invicti.com/blog/security-labs/brainstorm-tool-release-optimizing-web-fuzzing-with-local-llms/) 7 | 8 | ## Short Description 9 | 10 | Combines traditional web fuzzing techniques with AI-powered path generation to discover hidden endpoints, files, and directories in web applications. 11 | 12 | ## Screenshot 13 | ![screenshot](screenshot.png) 14 | 15 | ## Long Description 16 | 17 | This tool enhances traditional web fuzzing by using local AI language models (via Ollama) to generate intelligent guesses for potential paths and filenames. It works by: 18 | 19 | 1. Extracting initial links from the target website 20 | 2. Using AI to analyze the structure and suggest new potential paths 21 | 3. Fuzzing these paths using ffuf 22 | 4. Learning from discoveries to generate more targeted suggestions 23 | 5. Repeat 24 | 25 | There are 2 tools: 26 | - `fuzzer.py`: Main fuzzer focusing on general path discovery 27 | - `fuzzer_shortname.py`: Specialized variant for short filename discovery (e.g., legacy 8.3 format) 28 | 29 | ## Prerequisites 30 | 31 | - Python 3.6+ 32 | - ffuf (https://github.com/ffuf/ffuf) 33 | - Ollama (https://ollama.ai) 34 | - Required Python packages (see requirements.txt) 35 | 36 | ## Local Ollama models 37 | 38 | By default, the tool is using the model `qwen2.5-coder:latest`. 39 | This model (or other models you want to use) needs to be downloaded first. 40 | 41 | ```bash 42 | ollama pull qwen2.5-coder:latest 43 | ``` 44 | 45 | ## Installation 46 | 47 | ```bash 48 | # Clone the repository 49 | git clone https://github.com/Invicti-Security/brainstorm.git 50 | cd brainstorm 51 | 52 | # Install Python dependencies 53 | pip install -r requirements.txt 54 | 55 | # Ensure ffuf is installed and in your PATH 56 | # Ensure Ollama is running locally on port 11434 57 | ``` 58 | 59 | ## Usage 60 | 61 | ### Basic Usage 62 | 63 | ```bash 64 | # Basic fuzzing with default settings 65 | python fuzzer.py "ffuf -w ./fuzz.txt -u http://example.com/FUZZ" 66 | 67 | # Short filename fuzzing (specify the 8.3 filename as the last parameter) 68 | python fuzzer_shortname.py "ffuf -w ./fuzz.txt -u http://example.com/FUZZ" "BENCHM~1.PY" 69 | ``` 70 | 71 | ### Command Line Options 72 | 73 | #### Main Fuzzer (fuzzer.py) 74 | ``` 75 | --debug Enable debug mode 76 | --cycles N Number of fuzzing cycles to run (default: 50) 77 | --model NAME Ollama model to use (default: qwen2.5-coder:latest) 78 | --prompt-file PATH Path to prompt file (default: prompts/files.txt) 79 | --status-codes LIST Comma-separated list of status codes to consider successful 80 | (default: 200,301,302,303,307,308,403,401,500) 81 | ``` 82 | 83 | #### Short Filename Fuzzer (fuzzer_shortname.py) 84 | ``` 85 | --debug Enable debug mode 86 | --cycles N Number of fuzzing cycles to run (default: 50) 87 | --model NAME Ollama model to use (default: qwen2.5-coder:latest) 88 | --status-codes LIST Comma-separated list of status codes to consider successful 89 | ``` 90 | 91 | ### Examples 92 | 93 | ```bash 94 | # Run fuzzing with custom cycles and model 95 | python fuzzer.py "ffuf -w ./fuzz.txt -u http://target.com/FUZZ" --cycles 100 --model llama2:latest 96 | 97 | # Run short filename fuzzing targeting a specific file 98 | python fuzzer_shortname.py "ffuf -w ./fuzz.txt -u http://target.com/FUZZ" "document.pdf" --cycles 25 99 | 100 | # Benchmark different models and generate HTML report 101 | python benchmark.py 102 | ``` 103 | 104 | ## Output 105 | 106 | - Discovered paths are saved to `all_links.txt` 107 | - Short filenames are saved to `all_filenames.txt` 108 | - Real-time console output shows progress and discoveries 109 | 110 | ## Benchmarking Ollama LLM models 111 | 112 | I've compared the most popular local LLM models, you can find the [results here](https://github.com/Invicti-Security/brainstorm/blob/main/benchmark_report.md). 113 | -------------------------------------------------------------------------------- /benchmark_report.md: -------------------------------------------------------------------------------- 1 | Fuzzer Benchmark Results 2 | ======================== 3 | 4 | 5 | Generated on: 2024-11-23 09:19:32 6 | 7 | 8 | 9 | Summary 10 | ------- 11 | 12 | 13 | Total models tested: 8 14 | 15 | 16 | Best performing model: **qwen2.5-coder:14b** with 3 unique links 17 | 18 | 19 | 20 | 21 | Model: mistral:latest 22 | --------------------- 23 | 24 | 25 | Total unique links discovered: 9 26 | 27 | 28 | ### Links discovered across all runs: 29 | 30 | 31 | 32 | 33 | | Link | Found in # of runs | 34 | | --- | --- | 35 | | dashboard.jsp | 5/10 | 36 | | userLogin.jsp | 10/10 | 37 | | logout.jsp | 10/10 | 38 | | forgotPassword.jsp | 10/10 | 39 | | index.jsp | 10/10 | 40 | | checkout.jsp | 6/10 | 41 | | cart.jsp | 6/10 | 42 | | api/v2/products | 1/10 | 43 | | adminLogin.jsp | 1/10 | 44 | 45 | 46 | 47 | 48 | Model: llama3.1:latest 49 | ---------------------- 50 | 51 | 52 | Total unique links discovered: 12 53 | 54 | 55 | ### Links discovered across all runs: 56 | 57 | 58 | 59 | 60 | | Link | Found in # of runs | 61 | | --- | --- | 62 | | dashboard.jsp | 10/10 | 63 | | adminLogin.jsp | 10/10 | 64 | | userLogin.jsp | 10/10 | 65 | | logout.jsp | 10/10 | 66 | | userRegister.jsp | 6/10 | 67 | | api/v2/users | 7/10 | 68 | | forgotPassword.jsp | 10/10 | 69 | | index.jsp | 10/10 | 70 | | checkout.jsp | 1/10 | 71 | | api/v2/products | 2/10 | 72 | | cart.jsp | 1/10 | 73 | | contact.jsp | 1/10 | 74 | 75 | 76 | 77 | 78 | Model: llama3.2:latest 79 | ---------------------- 80 | 81 | 82 | Total unique links discovered: 11 83 | 84 | 85 | ### Links discovered across all runs: 86 | 87 | 88 | 89 | 90 | | Link | Found in # of runs | 91 | | --- | --- | 92 | | dashboard.jsp | 10/10 | 93 | | adminLogin.jsp | 5/10 | 94 | | userLogin.jsp | 10/10 | 95 | | logout.jsp | 10/10 | 96 | | userRegister.jsp | 5/10 | 97 | | cart.jsp | 9/10 | 98 | | checkout.jsp | 8/10 | 99 | | api/v2/products | 9/10 | 100 | | api/v2/users | 9/10 | 101 | | forgotPassword.jsp | 10/10 | 102 | | index.jsp | 10/10 | 103 | 104 | 105 | 106 | 107 | Model: qwen2.5:latest 108 | --------------------- 109 | 110 | 111 | Total unique links discovered: 10 112 | 113 | 114 | ### Links discovered across all runs: 115 | 116 | 117 | 118 | 119 | | Link | Found in # of runs | 120 | | --- | --- | 121 | | adminLogin.jsp | 9/10 | 122 | | userLogin.jsp | 10/10 | 123 | | logout.jsp | 10/10 | 124 | | userRegister.jsp | 10/10 | 125 | | api/v2/users | 10/10 | 126 | | forgotPassword.jsp | 10/10 | 127 | | index.jsp | 10/10 | 128 | | about.jsp | 2/10 | 129 | | dashboard.jsp | 9/10 | 130 | | contact.jsp | 5/10 | 131 | 132 | 133 | 134 | 135 | Model: qwen2.5-coder:latest 136 | --------------------------- 137 | 138 | 139 | Total unique links discovered: 12 140 | 141 | 142 | ### Links discovered across all runs: 143 | 144 | 145 | 146 | 147 | | Link | Found in # of runs | 148 | | --- | --- | 149 | | about.jsp | 10/10 | 150 | | dashboard.jsp | 10/10 | 151 | | userLogin.jsp | 10/10 | 152 | | logout.jsp | 10/10 | 153 | | userRegister.jsp | 8/10 | 154 | | checkout.jsp | 10/10 | 155 | | api/v2/products | 8/10 | 156 | | api/v2/users | 8/10 | 157 | | cart.jsp | 9/10 | 158 | | forgotPassword.jsp | 10/10 | 159 | | contact.jsp | 10/10 | 160 | | index.jsp | 10/10 | 161 | 162 | 163 | 164 | 165 | Model: qwen2.5-coder:14b 166 | ------------------------ 167 | 168 | 169 | Total unique links discovered: 13 170 | 171 | 172 | ### Links discovered across all runs: 173 | 174 | 175 | 176 | 177 | | Link | Found in # of runs | 178 | | --- | --- | 179 | | about.jsp | 10/10 | 180 | | dashboard.jsp | 10/10 | 181 | | adminLogin.jsp | 9/10 | 182 | | userLogin.jsp | 10/10 | 183 | | logout.jsp | 9/10 | 184 | | userRegister.jsp | 10/10 | 185 | | checkout.jsp | 10/10 | 186 | | cart.jsp | 10/10 | 187 | | forgotPassword.jsp | 10/10 | 188 | | index.jsp | 10/10 | 189 | | contact.jsp | 10/10 | 190 | | api/v2/products | 3/10 | 191 | | api/v2/users | 3/10 | 192 | 193 | 194 | 195 | 196 | Model: gemma:latest 197 | ------------------- 198 | 199 | 200 | Total unique links discovered: 9 201 | 202 | 203 | ### Links discovered across all runs: 204 | 205 | 206 | 207 | 208 | | Link | Found in # of runs | 209 | | --- | --- | 210 | | about.jsp | 10/10 | 211 | | dashboard.jsp | 10/10 | 212 | | userLogin.jsp | 10/10 | 213 | | logout.jsp | 5/10 | 214 | | checkout.jsp | 10/10 | 215 | | cart.jsp | 10/10 | 216 | | forgotPassword.jsp | 6/10 | 217 | | index.jsp | 10/10 | 218 | | contact.jsp | 10/10 | 219 | 220 | 221 | 222 | 223 | Model: phi3:latest 224 | ------------------ 225 | 226 | 227 | Total unique links discovered: 2 228 | 229 | 230 | ### Links discovered across all runs: 231 | 232 | 233 | 234 | 235 | | Link | Found in # of runs | 236 | | --- | --- | 237 | | userLogin.jsp | 10/10 | 238 | | index.jsp | 10/10 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /benchmark.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import json 3 | import os 4 | from collections import defaultdict 5 | from datetime import datetime 6 | from jinja2 import Template 7 | import time 8 | 9 | def run_fuzzer(model, run_number, total_runs): 10 | """Run the fuzzer with a specific model and return discovered links""" 11 | print(f"\nRunning benchmark for {model} - Run {run_number}/{total_runs}") 12 | 13 | # Remove previous all_links.txt if it exists 14 | if os.path.exists('all_links.txt'): 15 | os.remove('all_links.txt') 16 | 17 | cmd = f'python fuzzer.py "ffuf -w .\\fuzz.txt -u http://127.0.0.1:88/FUZZ -fc 403 -fw 4" --cycles 25 --model {model}' 18 | try: 19 | subprocess.run(cmd, shell=True, check=True) 20 | 21 | # Read discovered links 22 | if os.path.exists('all_links.txt'): 23 | with open('all_links.txt', 'r') as f: 24 | return set(line.strip() for line in f.readlines()) 25 | return set() 26 | except subprocess.CalledProcessError as e: 27 | print(f"Error running fuzzer: {e}") 28 | return set() 29 | 30 | def generate_html_report(results): 31 | """Generate an HTML report from the benchmark results""" 32 | template = """ 33 | 34 | 35 | 36 | Fuzzer Benchmark Results 37 | 46 | 47 | 48 |

Fuzzer Benchmark Results

49 |

Generated on: {{ timestamp }}

50 | 51 |
52 |

Summary

53 |

Total models tested: {{ results|length }}

54 | {% set best_model = get_best_model(results) %} 55 |

Best performing model: {{ best_model[0] }} with {{ best_model[1]|length }} unique links

56 |
57 | 58 | {% for model, data in results.items() %} 59 |
60 |

Model: {{ model }}

61 |

Total unique links discovered: {{ data.all_links|length }}

62 | 63 |

Links discovered across all runs:

64 | 65 | 66 | 67 | 68 | 69 | {% for link, count in data.frequency.items() %} 70 | 71 | 72 | 73 | 74 | {% endfor %} 75 |
LinkFound in # of runs
{{ link }}{{ count }}/10
76 |
77 | {% endfor %} 78 | 79 | 80 | """ 81 | 82 | # Calculate link frequency for each model 83 | for model_data in results.values(): 84 | model_data['frequency'] = defaultdict(int) 85 | for run_links in model_data['runs']: 86 | for link in run_links: 87 | model_data['frequency'][link] += 1 88 | # Convert defaultdict to regular dict 89 | model_data['frequency'] = dict(model_data['frequency']) 90 | 91 | def get_best_model(results): 92 | return max(results.items(), key=lambda x: len(x[1]['all_links'])) 93 | 94 | # Create the template and render 95 | template = Template(template) 96 | html_content = template.render( 97 | results=results, 98 | timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 99 | get_best_model=get_best_model 100 | ) 101 | 102 | # Write the report 103 | with open('benchmark_report.html', 'w') as f: 104 | f.write(html_content) 105 | 106 | def main(): 107 | # Read models from file 108 | try: 109 | with open('models.txt', 'r') as f: 110 | models = [line.strip() for line in f.readlines() if line.strip()] 111 | except FileNotFoundError: 112 | print("Error: models.txt file not found!") 113 | return 114 | 115 | results = {} 116 | runs_per_model = 10 117 | 118 | for model in models: 119 | print(f"\nBenchmarking model: {model}") 120 | model_results = { 121 | 'runs': [], 122 | 'all_links': set() 123 | } 124 | 125 | for run in range(1, runs_per_model + 1): 126 | discovered_links = run_fuzzer(model, run, runs_per_model) 127 | model_results['runs'].append(discovered_links) 128 | model_results['all_links'].update(discovered_links) 129 | 130 | # Small delay between runs 131 | time.sleep(1) 132 | 133 | results[model] = model_results 134 | 135 | # Generate the HTML report 136 | generate_html_report(results) 137 | print("\nBenchmark complete! Report generated as 'benchmark_report.html'") 138 | 139 | if __name__ == "__main__": 140 | main() 141 | -------------------------------------------------------------------------------- /fuzzer_shortname.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import requests 4 | import json 5 | import subprocess 6 | import re 7 | import logging 8 | import argparse 9 | import random 10 | from colorama import init, Fore, Style 11 | 12 | # Initialize colorama 13 | init() 14 | 15 | # Configure logging 16 | logger = logging.getLogger(__name__) 17 | handlers = [logging.StreamHandler()] 18 | 19 | logging.basicConfig( 20 | level=logging.INFO, 21 | format='%(asctime)s - %(levelname)s - %(message)s', 22 | handlers=handlers 23 | ) 24 | if '--debug' in sys.argv: 25 | logger.setLevel(logging.DEBUG) 26 | 27 | def call_ollama(prompt, model="qwen2.5-coder:latest"): 28 | """Call the local Ollama API""" 29 | try: 30 | logger.info(f"Calling Ollama API with model {model}") 31 | response = requests.post('http://localhost:11434/api/generate', 32 | json={ 33 | "model": model, 34 | "prompt": prompt, 35 | "stream": False 36 | }) 37 | response.raise_for_status() 38 | result = response.json()['response'] 39 | logger.info("Successfully received response from Ollama API") 40 | return result 41 | except requests.RequestException as e: 42 | logger.error(f"Network error calling Ollama API: {e}") 43 | return "" 44 | except Exception as e: 45 | logger.error(f"Unexpected error calling Ollama API: {e}", exc_info=True) 46 | return "" 47 | 48 | def extract_filenames(response): 49 | """Extract filenames from between tags""" 50 | pattern = r'(.*?)' 51 | match = re.search(pattern, response, re.DOTALL) 52 | if match: 53 | # Split on newlines and remove empty lines and whitespace 54 | return [line.strip() for line in match.group(1).split('\n') if line.strip()] 55 | return [] 56 | 57 | def run_ffuf(original_cmd, wordlist, target_url): 58 | """Run ffuf and return results""" 59 | try: 60 | # Split original command into parts 61 | cmd_parts = original_cmd.split() 62 | # Remove the original -u and -w arguments and their values 63 | i = 0 64 | while i < len(cmd_parts): 65 | if cmd_parts[i] in ['-u', '-w']: 66 | cmd_parts.pop(i) # Remove the argument 67 | if i < len(cmd_parts): # Remove its value 68 | cmd_parts.pop(i) 69 | continue 70 | i += 1 71 | 72 | # Remove 'ffuf' if it's the first part 73 | if cmd_parts and cmd_parts[0] == 'ffuf': 74 | cmd_parts = cmd_parts[1:] 75 | 76 | # Construct new command with all original arguments 77 | ffuf_cmd = ['ffuf'] + cmd_parts + ['-w', wordlist, '-u', target_url, '-o', 'output.json'] 78 | logger.info(f"Running ffuf command: {' '.join(ffuf_cmd)}") 79 | 80 | result = subprocess.run(ffuf_cmd, capture_output=True, text=True) 81 | if result.returncode != 0: 82 | logger.error(f"ffuf command failed: {result.stderr}") 83 | return None 84 | 85 | if not os.path.exists('output.json') or os.stat('output.json').st_size == 0: 86 | logger.warning("ffuf produced no output") 87 | return None 88 | 89 | with open('output.json', 'r') as f: 90 | data = json.load(f) 91 | return data 92 | except FileNotFoundError: 93 | logger.error("ffuf command not found. Please ensure it's installed and in PATH") 94 | return None 95 | except json.JSONDecodeError as e: 96 | logger.error(f"Error parsing ffuf output JSON: {e}") 97 | return None 98 | except Exception as e: 99 | logger.error(f"Unexpected error running ffuf: {e}", exc_info=True) 100 | return None 101 | 102 | def display_results(tested_filenames, new_discovered_filenames): 103 | """Display final results and statistics""" 104 | print(f"\n{Fore.YELLOW}=== Final Results ==={Style.RESET_ALL}") 105 | print(f"{Fore.CYAN}Total filenames tested with ffuf:{Style.RESET_ALL} {len(tested_filenames)}") 106 | print(f"{Fore.CYAN}Total new filenames discovered:{Style.RESET_ALL} {len(new_discovered_filenames)}") 107 | if new_discovered_filenames: 108 | print(f"\n{Fore.YELLOW}New discovered filenames (via ffuf):{Style.RESET_ALL}") 109 | for filename in sorted(new_discovered_filenames): 110 | print(f" {Fore.GREEN}{filename}{Style.RESET_ALL}") 111 | else: 112 | print(f"\n{Fore.YELLOW}No new filenames were discovered{Style.RESET_ALL}") 113 | 114 | def main(): 115 | logger.info("Starting shortname fuzzer application") 116 | 117 | # Set up argument parser 118 | parser = argparse.ArgumentParser(description='Shortname Fuzzer with Ollama integration') 119 | parser.add_argument('command', help='ffuf command to run') 120 | parser.add_argument('filename', help='Filename to use in the prompt') 121 | parser.add_argument('--debug', action='store_true', help='Enable debug mode') 122 | parser.add_argument('--cycles', type=int, default=50, help='Number of fuzzing cycles to run (default: 50)') 123 | parser.add_argument('--model', default='qwen2.5-coder:latest', help='Ollama model to use (default: qwen2.5:latest)') 124 | parser.add_argument('--status-codes', type=str, default='200,301,302,303,307,308,403,401,500', 125 | help='Comma-separated list of status codes to consider as successful (default: 200,301,302,303,307,308,403,401,500)') 126 | 127 | args = parser.parse_args() 128 | 129 | # Parse command line argument 130 | cmd = args.command 131 | # Try to match URL with double quotes, single quotes, or no quotes 132 | url_match = re.search(r'-u\s*["\']?([^"\'\s]+)["\']?', cmd) 133 | if not url_match: 134 | logger.error("Could not extract URL from command") 135 | print("Could not extract URL from command") 136 | return 137 | 138 | logger.info(f"Extracted base URL from command: {url_match.group(1)}") 139 | 140 | considered_status_codes = [int(code) for code in args.status_codes.split(',')] 141 | 142 | all_filenames = set() # all filenames including tried ones 143 | new_discovered_filenames = set() # only filenames discovered by ffuf 144 | tested_filenames = set() # filenames that were tested with ffuf 145 | 146 | # Read prompt template 147 | try: 148 | with open('prompts/shortname.txt', 'r') as f: 149 | ollama_prompt = f.read() 150 | except Exception as e: 151 | print(f"Error reading prompt file: {e}") 152 | return 153 | 154 | cycle = 0 155 | max_cycles = args.cycles 156 | 157 | while cycle < max_cycles: 158 | try: 159 | print(f"\nCycle {cycle + 1}/{max_cycles}") 160 | 161 | # Prepare prompt with filename 162 | current_prompt = ollama_prompt.replace('{{INPUT_83_FILENAME}}', args.filename) 163 | 164 | # Call Ollama API 165 | if args.debug: 166 | logger.debug("\nSending prompt to Ollama:") 167 | logger.debug(current_prompt) 168 | 169 | response = call_ollama(current_prompt, model=args.model) 170 | 171 | if args.debug: 172 | logger.debug("\nOllama response:") 173 | logger.debug(response) 174 | 175 | new_filenames = extract_filenames(response) 176 | # print new filenames 177 | print(f"\n{Fore.YELLOW}New filenames suggested by Ollama:{Style.RESET_ALL}") 178 | for filename in new_filenames: 179 | print(f" {Fore.GREEN}{filename}{Style.RESET_ALL}") 180 | 181 | # Update filenames 182 | # Filter out filenames that have already been tested 183 | new_unique_filenames = set(new_filenames) - all_filenames 184 | untested_filenames = new_unique_filenames - tested_filenames 185 | 186 | # add to tested filenames 187 | tested_filenames.update(new_unique_filenames) 188 | 189 | if untested_filenames: 190 | if args.debug: 191 | logger.debug("\nNew untested filenames suggested by Ollama:") 192 | for filename in untested_filenames: 193 | logger.debug(f" {filename}") 194 | with open('links.txt', 'w') as f: 195 | f.write('\n'.join(untested_filenames)) 196 | 197 | # Run ffuf with original command 198 | ffuf_results = run_ffuf(cmd, './links.txt', url_match.group(1)) 199 | if ffuf_results and 'results' in ffuf_results: 200 | if args.debug: 201 | logger.debug("\nffuf results:") 202 | logger.debug(json.dumps(ffuf_results, indent=2)) 203 | 204 | fuzz_filenames = set() 205 | for result in ffuf_results['results']: 206 | tested_filename = result['input']['FUZZ'] 207 | if result['status'] in considered_status_codes: 208 | fuzz_filenames.add(tested_filename) 209 | 210 | # Update discovered filenames 211 | new_discovered = fuzz_filenames - all_filenames 212 | if new_discovered: 213 | print(f"\n{Fore.YELLOW}New filenames discovered:{Style.RESET_ALL}") 214 | for filename in new_discovered: 215 | print(f" {Fore.GREEN}{filename}{Style.RESET_ALL}") 216 | all_filenames.update(new_discovered) 217 | new_discovered_filenames.update(new_discovered) 218 | 219 | # Save all discovered filenames to file 220 | with open('all_filenames.txt', 'w') as f: 221 | f.write('\n'.join(sorted(all_filenames))) 222 | 223 | cycle += 1 224 | 225 | except KeyboardInterrupt: 226 | logger.info("Fuzzer stopped by user") 227 | print(f"\n{Fore.RED}Stopping fuzzer...{Style.RESET_ALL}") 228 | display_results(tested_filenames, new_discovered_filenames) 229 | break 230 | except Exception as e: 231 | print(f"Error in cycle {cycle}: {e}") 232 | continue 233 | 234 | # Display final results after all cycles complete 235 | if cycle >= max_cycles: 236 | display_results(tested_filenames, new_discovered_filenames) 237 | 238 | if __name__ == "__main__": 239 | main() 240 | -------------------------------------------------------------------------------- /fuzzer.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import requests 4 | import json 5 | import subprocess 6 | import re 7 | import logging 8 | import argparse 9 | import random 10 | from bs4 import BeautifulSoup 11 | from urllib.parse import urljoin, urlparse 12 | from colorama import init, Fore, Style 13 | 14 | # Initialize colorama 15 | init() 16 | 17 | # Configure logging 18 | logger = logging.getLogger(__name__) 19 | handlers = [logging.StreamHandler()] 20 | 21 | logging.basicConfig( 22 | level=logging.INFO, 23 | format='%(asctime)s - %(levelname)s - %(message)s', 24 | handlers=handlers 25 | ) 26 | if '--debug' in sys.argv: 27 | logger.setLevel(logging.DEBUG) 28 | 29 | def extract_links(url): 30 | """Extract first 25 links from the main page""" 31 | try: 32 | logger.info(f"Attempting to extract links from {url}") 33 | response = requests.get(url) 34 | soup = BeautifulSoup(response.text, 'html.parser') 35 | links = [] 36 | for a in soup.find_all('a', href=True)[:25]: 37 | href = a['href'] 38 | # Extract path without leading slash 39 | if href and not href.startswith(('#', 'javascript:', 'mailto:')): 40 | # Remove any URL prefix 41 | if href.startswith(('http://', 'https://')): 42 | href = urlparse(href).path 43 | # Remove leading slash if present 44 | if href.startswith('/'): 45 | href = href[1:] 46 | # Only add if it's not empty 47 | if href: 48 | links.append(href) 49 | logger.info(f"Successfully extracted {len(links)} links") 50 | return links, dict(response.headers) 51 | except requests.RequestException as e: 52 | logger.error(f"Network error while extracting links: {e}") 53 | return [], {} 54 | except Exception as e: 55 | logger.error(f"Unexpected error while extracting links: {e}", exc_info=True) 56 | return [], {} 57 | 58 | def format_headers(headers): 59 | """Format headers in the required format""" 60 | formatted = [] 61 | formatted.append("HTTP/1.1 200") 62 | for key, value in headers.items(): 63 | formatted.append(f"{key}: {value}") 64 | return "\n".join(formatted) 65 | 66 | def call_ollama(prompt, model="qwen2.5-coder:latest"): 67 | """Call the local Ollama API""" 68 | try: 69 | logger.info(f"Calling Ollama API with model {model}") 70 | response = requests.post('http://localhost:11434/api/generate', 71 | json={ 72 | "model": model, 73 | "prompt": prompt, 74 | "stream": False 75 | }) 76 | response.raise_for_status() 77 | result = response.json()['response'] 78 | logger.info("Successfully received response from Ollama API") 79 | return result 80 | except requests.RequestException as e: 81 | logger.error(f"Network error calling Ollama API: {e}") 82 | return "" 83 | except Exception as e: 84 | logger.error(f"Unexpected error calling Ollama API: {e}", exc_info=True) 85 | return "" 86 | 87 | def extract_new_links(response): 88 | """Extract links from between tags""" 89 | pattern = r'(.*?)' 90 | match = re.search(pattern, response, re.DOTALL) 91 | if match: 92 | # Split on newlines and remove empty lines and whitespace 93 | return [line.strip() for line in match.group(1).split('\n') if line.strip()] 94 | return [] 95 | 96 | def run_ffuf(original_cmd, wordlist, target_url): 97 | """Run ffuf and return results""" 98 | try: 99 | # Split original command into parts 100 | cmd_parts = original_cmd.split() 101 | # Remove the original -u and -w arguments and their values 102 | i = 0 103 | while i < len(cmd_parts): 104 | if cmd_parts[i] in ['-u', '-w']: 105 | cmd_parts.pop(i) # Remove the argument 106 | if i < len(cmd_parts): # Remove its value 107 | cmd_parts.pop(i) 108 | continue 109 | i += 1 110 | 111 | # Remove 'ffuf' if it's the first part 112 | if cmd_parts and cmd_parts[0] == 'ffuf': 113 | cmd_parts = cmd_parts[1:] 114 | 115 | # Construct new command with all original arguments 116 | ffuf_cmd = ['ffuf'] + cmd_parts + ['-w', wordlist, '-u', target_url, '-o', 'output.json'] 117 | logger.info(f"Running ffuf command: {' '.join(ffuf_cmd)}") 118 | 119 | result = subprocess.run(ffuf_cmd, capture_output=True, text=True) 120 | if result.returncode != 0: 121 | logger.error(f"ffuf command failed: {result.stderr}") 122 | return None 123 | 124 | if not os.path.exists('output.json') or os.stat('output.json').st_size == 0: 125 | logger.warning("ffuf produced no output") 126 | return None 127 | 128 | with open('output.json', 'r') as f: 129 | data = json.load(f) 130 | #logger.info(f"Successfully parsed ffuf results") 131 | return data 132 | except FileNotFoundError: 133 | logger.error("ffuf command not found. Please ensure it's installed and in PATH") 134 | return None 135 | except json.JSONDecodeError as e: 136 | logger.error(f"Error parsing ffuf output JSON: {e}") 137 | return None 138 | except Exception as e: 139 | logger.error(f"Unexpected error running ffuf: {e}", exc_info=True) 140 | return None 141 | 142 | def display_results(tested_links, new_discovered_links): 143 | """Display final results and statistics""" 144 | print(f"\n{Fore.YELLOW}=== Final Results ==={Style.RESET_ALL}") 145 | print(f"{Fore.CYAN}Total links tested with ffuf:{Style.RESET_ALL} {len(tested_links)}") 146 | print(f"{Fore.CYAN}Total new links discovered:{Style.RESET_ALL} {len(new_discovered_links)}") 147 | if new_discovered_links: 148 | print(f"\n{Fore.YELLOW}New discovered links (via ffuf):{Style.RESET_ALL}") 149 | for link in sorted(new_discovered_links): 150 | print(f" {Fore.GREEN}{link}{Style.RESET_ALL}") 151 | else: 152 | print(f"\n{Fore.YELLOW}No new links were discovered{Style.RESET_ALL}") 153 | 154 | def main(): 155 | logger.info("Starting fuzzer application") 156 | 157 | # Set up argument parser 158 | parser = argparse.ArgumentParser(description='Web Fuzzer with Ollama integration') 159 | parser.add_argument('command', help='ffuf command to run') 160 | parser.add_argument('--debug', action='store_true', help='Enable debug mode') 161 | parser.add_argument('--cycles', type=int, default=50, help='Number of fuzzing cycles to run (default: 50)') 162 | parser.add_argument('--model', default='qwen2.5-coder:latest', help='Ollama model to use (default: qwen2.5-coder:latest)') 163 | parser.add_argument('--prompt-file', default='prompts/files.txt', help='Path to prompt file (default: prompts/files.txt)') 164 | parser.add_argument('--status-codes', type=str, default='200,301,302,303,307,308,403,401,500', 165 | help='Comma-separated list of status codes to consider as successful (default: 200,301,302,303,307,308,403,401,500)') 166 | 167 | args = parser.parse_args() 168 | 169 | # Parse command line argument 170 | cmd = sys.argv[1] 171 | # Try to match URL with double quotes, single quotes, or no quotes 172 | url_match = re.search(r'-u\s*["\']?([^"\'\s]+)["\']?', cmd) 173 | if not url_match: 174 | logger.error("Could not extract URL from command") 175 | print("Could not extract URL from command") 176 | return 177 | 178 | logger.info(f"Extracted base URL from command: {url_match.group(1)}") 179 | 180 | base_url = url_match.group(1).replace('FUZZ', '') 181 | considered_status_codes = [int(code) for code in args.status_codes.split(',')] 182 | 183 | # Initial setup 184 | initial_links, headers = extract_links(base_url) 185 | unique_initial_links = set(initial_links) 186 | print(f"\n{Fore.LIGHTBLACK_EX}Initial unique links extracted from website:{Style.RESET_ALL}") 187 | for link in sorted(unique_initial_links): 188 | print(f" {Fore.LIGHTBLACK_EX}{link}{Style.RESET_ALL}") 189 | print() 190 | 191 | all_links = set(initial_links) # all links including tried ones 192 | new_discovered_links = set() # only links discovered by ffuf 193 | tested_links = set() # links that were tested with ffuf 194 | server_headers = format_headers(headers) 195 | 196 | # Read prompt template from specified file 197 | try: 198 | with open(args.prompt_file, 'r') as f: 199 | ollama_prompt = f.read() 200 | except Exception as e: 201 | print(f"Error reading prompt file: {e}") 202 | return 203 | 204 | cycle = 0 205 | max_cycles = args.cycles 206 | 207 | while cycle < max_cycles: 208 | try: 209 | #logger.info(f"Starting cycle {cycle + 1}/{max_cycles}") 210 | print(f"\nCycle {cycle + 1}/{max_cycles}") 211 | 212 | # Prepare prompt with randomized link order 213 | randomized_links = list(all_links) 214 | random.shuffle(randomized_links) 215 | current_prompt = ollama_prompt.replace('{{initialLinks}}', 216 | '\n'.join(randomized_links)) 217 | current_prompt = current_prompt.replace('{{serverHeaders}}', 218 | server_headers) 219 | 220 | # Call Ollama API 221 | if args.debug: 222 | logger.debug("\nSending prompt to Ollama:") 223 | logger.debug(current_prompt) 224 | 225 | response = call_ollama(current_prompt, model=args.model) 226 | 227 | if args.debug: 228 | logger.debug("\nOllama response:") 229 | logger.debug(response) 230 | 231 | new_links = extract_new_links(response) 232 | 233 | # Update links 234 | # Filter out links that have already been tested 235 | new_unique_links = set(new_links) - all_links 236 | untested_links = new_unique_links - tested_links 237 | 238 | # add to tested links 239 | tested_links.update(new_unique_links) 240 | 241 | if untested_links: 242 | if args.debug: 243 | logger.debug("\nNew untested links suggested by Ollama:") 244 | for link in untested_links: 245 | logger.debug(f" {link}") 246 | with open('links.txt', 'w') as f: 247 | f.write('\n'.join(untested_links)) 248 | 249 | # Run ffuf with original command 250 | ffuf_results = run_ffuf(cmd, './links.txt', url_match.group(1)) 251 | if ffuf_results and 'results' in ffuf_results: 252 | if args.debug: 253 | logger.debug("\nffuf results:") 254 | logger.debug(json.dumps(ffuf_results, indent=2)) 255 | 256 | fuzz_links = set() 257 | for result in ffuf_results['results']: 258 | tested_url = result['input']['FUZZ'] 259 | if result['status'] in considered_status_codes: 260 | fuzz_links.add(tested_url) 261 | 262 | # Update discovered links 263 | new_discovered = fuzz_links - all_links 264 | if new_discovered: 265 | print(f"\n{Fore.YELLOW}New links discovered:{Style.RESET_ALL}") 266 | for link in new_discovered: 267 | print(f" {Fore.GREEN}{link}{Style.RESET_ALL}") 268 | all_links.update(new_discovered) 269 | new_discovered_links.update(new_discovered) 270 | 271 | # Save all discovered links to file 272 | with open('all_links.txt', 'w') as f: 273 | f.write('\n'.join(sorted(all_links))) 274 | 275 | cycle += 1 276 | 277 | except KeyboardInterrupt: 278 | logger.info("Fuzzer stopped by user") 279 | print(f"\n{Fore.RED}Stopping fuzzer...{Style.RESET_ALL}") 280 | display_results(tested_links, new_discovered_links) 281 | break 282 | except Exception as e: 283 | print(f"Error in cycle {cycle}: {e}") 284 | continue 285 | 286 | # Display final results after all cycles complete 287 | if cycle >= max_cycles: 288 | display_results(tested_links, new_discovered_links) 289 | 290 | if __name__ == "__main__": 291 | main() 292 | -------------------------------------------------------------------------------- /benchmark_report.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fuzzer Benchmark Results 6 | 15 | 16 | 17 |

Fuzzer Benchmark Results

18 |

Generated on: 2024-11-23 09:19:32

19 | 20 |
21 |

Summary

22 |

Total models tested: 8

23 | 24 |

Best performing model: qwen2.5-coder:14b with 3 unique links

25 |
26 | 27 | 28 |
29 |

Model: mistral:latest

30 |

Total unique links discovered: 9

31 | 32 |

Links discovered across all runs:

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
LinkFound in # of runs
dashboard.jsp5/10
userLogin.jsp10/10
logout.jsp10/10
forgotPassword.jsp10/10
index.jsp10/10
checkout.jsp6/10
cart.jsp6/10
api/v2/products1/10
adminLogin.jsp1/10
85 |
86 | 87 |
88 |

Model: llama3.1:latest

89 |

Total unique links discovered: 12

90 | 91 |

Links discovered across all runs:

92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 |
LinkFound in # of runs
dashboard.jsp10/10
adminLogin.jsp10/10
userLogin.jsp10/10
logout.jsp10/10
userRegister.jsp6/10
api/v2/users7/10
forgotPassword.jsp10/10
index.jsp10/10
checkout.jsp1/10
api/v2/products2/10
cart.jsp1/10
contact.jsp1/10
159 |
160 | 161 |
162 |

Model: llama3.2:latest

163 |

Total unique links discovered: 11

164 | 165 |

Links discovered across all runs:

166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 |
LinkFound in # of runs
dashboard.jsp10/10
adminLogin.jsp5/10
userLogin.jsp10/10
logout.jsp10/10
userRegister.jsp5/10
cart.jsp9/10
checkout.jsp8/10
api/v2/products9/10
api/v2/users9/10
forgotPassword.jsp10/10
index.jsp10/10
229 |
230 | 231 |
232 |

Model: qwen2.5:latest

233 |

Total unique links discovered: 10

234 | 235 |

Links discovered across all runs:

236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 |
LinkFound in # of runs
adminLogin.jsp9/10
userLogin.jsp10/10
logout.jsp10/10
userRegister.jsp10/10
api/v2/users10/10
forgotPassword.jsp10/10
index.jsp10/10
about.jsp2/10
dashboard.jsp9/10
contact.jsp5/10
293 |
294 | 295 |
296 |

Model: qwen2.5-coder:latest

297 |

Total unique links discovered: 12

298 | 299 |

Links discovered across all runs:

300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 |
LinkFound in # of runs
about.jsp10/10
dashboard.jsp10/10
userLogin.jsp10/10
logout.jsp10/10
userRegister.jsp8/10
checkout.jsp10/10
api/v2/products8/10
api/v2/users8/10
cart.jsp9/10
forgotPassword.jsp10/10
contact.jsp10/10
index.jsp10/10
367 |
368 | 369 |
370 |

Model: qwen2.5-coder:14b

371 |

Total unique links discovered: 13

372 | 373 |

Links discovered across all runs:

374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 |
LinkFound in # of runs
about.jsp10/10
dashboard.jsp10/10
adminLogin.jsp9/10
userLogin.jsp10/10
logout.jsp9/10
userRegister.jsp10/10
checkout.jsp10/10
cart.jsp10/10
forgotPassword.jsp10/10
index.jsp10/10
contact.jsp10/10
api/v2/products3/10
api/v2/users3/10
446 |
447 | 448 |
449 |

Model: gemma:latest

450 |

Total unique links discovered: 9

451 | 452 |

Links discovered across all runs:

453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 |
LinkFound in # of runs
about.jsp10/10
dashboard.jsp10/10
userLogin.jsp10/10
logout.jsp5/10
checkout.jsp10/10
cart.jsp10/10
forgotPassword.jsp6/10
index.jsp10/10
contact.jsp10/10
505 |
506 | 507 |
508 |

Model: phi3:latest

509 |

Total unique links discovered: 2

510 | 511 |

Links discovered across all runs:

512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 |
LinkFound in # of runs
userLogin.jsp10/10
index.jsp10/10
529 |
530 | 531 | 532 | 533 | --------------------------------------------------------------------------------