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 | | Link |
36 | Found in # of runs |
37 |
38 |
39 |
40 | | dashboard.jsp |
41 | 5/10 |
42 |
43 |
44 |
45 | | userLogin.jsp |
46 | 10/10 |
47 |
48 |
49 |
50 | | logout.jsp |
51 | 10/10 |
52 |
53 |
54 |
55 | | forgotPassword.jsp |
56 | 10/10 |
57 |
58 |
59 |
60 | | index.jsp |
61 | 10/10 |
62 |
63 |
64 |
65 | | checkout.jsp |
66 | 6/10 |
67 |
68 |
69 |
70 | | cart.jsp |
71 | 6/10 |
72 |
73 |
74 |
75 | | api/v2/products |
76 | 1/10 |
77 |
78 |
79 |
80 | | adminLogin.jsp |
81 | 1/10 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
Model: llama3.1:latest
89 |
Total unique links discovered: 12
90 |
91 |
Links discovered across all runs:
92 |
93 |
94 | | Link |
95 | Found in # of runs |
96 |
97 |
98 |
99 | | dashboard.jsp |
100 | 10/10 |
101 |
102 |
103 |
104 | | adminLogin.jsp |
105 | 10/10 |
106 |
107 |
108 |
109 | | userLogin.jsp |
110 | 10/10 |
111 |
112 |
113 |
114 | | logout.jsp |
115 | 10/10 |
116 |
117 |
118 |
119 | | userRegister.jsp |
120 | 6/10 |
121 |
122 |
123 |
124 | | api/v2/users |
125 | 7/10 |
126 |
127 |
128 |
129 | | forgotPassword.jsp |
130 | 10/10 |
131 |
132 |
133 |
134 | | index.jsp |
135 | 10/10 |
136 |
137 |
138 |
139 | | checkout.jsp |
140 | 1/10 |
141 |
142 |
143 |
144 | | api/v2/products |
145 | 2/10 |
146 |
147 |
148 |
149 | | cart.jsp |
150 | 1/10 |
151 |
152 |
153 |
154 | | contact.jsp |
155 | 1/10 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
Model: llama3.2:latest
163 |
Total unique links discovered: 11
164 |
165 |
Links discovered across all runs:
166 |
167 |
168 | | Link |
169 | Found in # of runs |
170 |
171 |
172 |
173 | | dashboard.jsp |
174 | 10/10 |
175 |
176 |
177 |
178 | | adminLogin.jsp |
179 | 5/10 |
180 |
181 |
182 |
183 | | userLogin.jsp |
184 | 10/10 |
185 |
186 |
187 |
188 | | logout.jsp |
189 | 10/10 |
190 |
191 |
192 |
193 | | userRegister.jsp |
194 | 5/10 |
195 |
196 |
197 |
198 | | cart.jsp |
199 | 9/10 |
200 |
201 |
202 |
203 | | checkout.jsp |
204 | 8/10 |
205 |
206 |
207 |
208 | | api/v2/products |
209 | 9/10 |
210 |
211 |
212 |
213 | | api/v2/users |
214 | 9/10 |
215 |
216 |
217 |
218 | | forgotPassword.jsp |
219 | 10/10 |
220 |
221 |
222 |
223 | | index.jsp |
224 | 10/10 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
Model: qwen2.5:latest
233 |
Total unique links discovered: 10
234 |
235 |
Links discovered across all runs:
236 |
237 |
238 | | Link |
239 | Found in # of runs |
240 |
241 |
242 |
243 | | adminLogin.jsp |
244 | 9/10 |
245 |
246 |
247 |
248 | | userLogin.jsp |
249 | 10/10 |
250 |
251 |
252 |
253 | | logout.jsp |
254 | 10/10 |
255 |
256 |
257 |
258 | | userRegister.jsp |
259 | 10/10 |
260 |
261 |
262 |
263 | | api/v2/users |
264 | 10/10 |
265 |
266 |
267 |
268 | | forgotPassword.jsp |
269 | 10/10 |
270 |
271 |
272 |
273 | | index.jsp |
274 | 10/10 |
275 |
276 |
277 |
278 | | about.jsp |
279 | 2/10 |
280 |
281 |
282 |
283 | | dashboard.jsp |
284 | 9/10 |
285 |
286 |
287 |
288 | | contact.jsp |
289 | 5/10 |
290 |
291 |
292 |
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 | | Link |
303 | Found in # of runs |
304 |
305 |
306 |
307 | | about.jsp |
308 | 10/10 |
309 |
310 |
311 |
312 | | dashboard.jsp |
313 | 10/10 |
314 |
315 |
316 |
317 | | userLogin.jsp |
318 | 10/10 |
319 |
320 |
321 |
322 | | logout.jsp |
323 | 10/10 |
324 |
325 |
326 |
327 | | userRegister.jsp |
328 | 8/10 |
329 |
330 |
331 |
332 | | checkout.jsp |
333 | 10/10 |
334 |
335 |
336 |
337 | | api/v2/products |
338 | 8/10 |
339 |
340 |
341 |
342 | | api/v2/users |
343 | 8/10 |
344 |
345 |
346 |
347 | | cart.jsp |
348 | 9/10 |
349 |
350 |
351 |
352 | | forgotPassword.jsp |
353 | 10/10 |
354 |
355 |
356 |
357 | | contact.jsp |
358 | 10/10 |
359 |
360 |
361 |
362 | | index.jsp |
363 | 10/10 |
364 |
365 |
366 |
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 | | Link |
377 | Found in # of runs |
378 |
379 |
380 |
381 | | about.jsp |
382 | 10/10 |
383 |
384 |
385 |
386 | | dashboard.jsp |
387 | 10/10 |
388 |
389 |
390 |
391 | | adminLogin.jsp |
392 | 9/10 |
393 |
394 |
395 |
396 | | userLogin.jsp |
397 | 10/10 |
398 |
399 |
400 |
401 | | logout.jsp |
402 | 9/10 |
403 |
404 |
405 |
406 | | userRegister.jsp |
407 | 10/10 |
408 |
409 |
410 |
411 | | checkout.jsp |
412 | 10/10 |
413 |
414 |
415 |
416 | | cart.jsp |
417 | 10/10 |
418 |
419 |
420 |
421 | | forgotPassword.jsp |
422 | 10/10 |
423 |
424 |
425 |
426 | | index.jsp |
427 | 10/10 |
428 |
429 |
430 |
431 | | contact.jsp |
432 | 10/10 |
433 |
434 |
435 |
436 | | api/v2/products |
437 | 3/10 |
438 |
439 |
440 |
441 | | api/v2/users |
442 | 3/10 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
Model: gemma:latest
450 |
Total unique links discovered: 9
451 |
452 |
Links discovered across all runs:
453 |
454 |
455 | | Link |
456 | Found in # of runs |
457 |
458 |
459 |
460 | | about.jsp |
461 | 10/10 |
462 |
463 |
464 |
465 | | dashboard.jsp |
466 | 10/10 |
467 |
468 |
469 |
470 | | userLogin.jsp |
471 | 10/10 |
472 |
473 |
474 |
475 | | logout.jsp |
476 | 5/10 |
477 |
478 |
479 |
480 | | checkout.jsp |
481 | 10/10 |
482 |
483 |
484 |
485 | | cart.jsp |
486 | 10/10 |
487 |
488 |
489 |
490 | | forgotPassword.jsp |
491 | 6/10 |
492 |
493 |
494 |
495 | | index.jsp |
496 | 10/10 |
497 |
498 |
499 |
500 | | contact.jsp |
501 | 10/10 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
Model: phi3:latest
509 |
Total unique links discovered: 2
510 |
511 |
Links discovered across all runs:
512 |
513 |
514 | | Link |
515 | Found in # of runs |
516 |
517 |
518 |
519 | | userLogin.jsp |
520 | 10/10 |
521 |
522 |
523 |
524 | | index.jsp |
525 | 10/10 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
--------------------------------------------------------------------------------