├── README.md ├── nuclei_sort.py └── requirements.txt /README.md: -------------------------------------------------------------------------------- 1 | # nuclei-scan-sort 2 | 3 | Simple Python script to sort nuclei scans by severity and URL 4 | 5 | 1. Install dependencies: 6 | ``` 7 | pip3 install -r requirements.txt 8 | ``` 9 | 2. Simply save nuclei scan with -o option: 10 | ``` 11 | nuclei -l targets.txt -o scan.txt 12 | ``` 13 | 3. Proceed it with script: 14 | ``` 15 | python3 nuclei_sort.py -i scan.txt 16 | ``` 17 | ![sorted](https://user-images.githubusercontent.com/58632878/218885613-2fb46456-ef8e-41c9-92b8-23a150436319.jpg) 18 | -------------------------------------------------------------------------------- /nuclei_sort.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from urllib.parse import urlparse 3 | from colorama import Fore, Style, init 4 | from argparse import ArgumentParser 5 | 6 | 7 | def sort_by_url(scan_results): 8 | try: 9 | return sorted(scan_results, key=lambda x: x.split(" ")[3]) 10 | except IndexError: 11 | # Check if scan file is valid 12 | print(Fore.RED + 'Not valid Nuclei Scan format' + Style.RESET_ALL) 13 | sys.exit(1) 14 | 15 | 16 | def sort_by_severity(scan_results): 17 | severity_order = {'[critical]': 1, '[high]': 2, '[medium]': 3, '[low]': 4, '[info]': 5, '[unknown]':6, '[]':7} 18 | return sorted(scan_results, key=lambda x: severity_order[x.split(" ")[2]]) 19 | 20 | 21 | def sort_by_all(scan_results): 22 | unique_domains = {} 23 | sorted_results = [] 24 | sorted_by_url = sort_by_url(scan_results) 25 | 26 | for i in sorted_by_url: 27 | # Find all urls 28 | parts_of_url = urlparse(i.split(" ")[3]) 29 | domains = [] 30 | # Add to "domains" list parts of url without http:// or https:// 31 | if parts_of_url.netloc != '': 32 | domains.append(parts_of_url.netloc) 33 | else: 34 | domains.append(parts_of_url.path) 35 | # Create new list to split all unique domains 36 | for domain in domains: 37 | if domain not in unique_domains: 38 | unique_domains[domain] = [i] 39 | else: 40 | unique_domains[domain].append(i) 41 | 42 | # Sort by severity for all unique domains 43 | for i in unique_domains.values(): 44 | by_severity = sort_by_severity(i) 45 | # Add delimiter between different domains 46 | by_severity[-1] += '\n' 47 | for j in by_severity: 48 | sorted_results.append(j) 49 | 50 | return sorted_results 51 | 52 | 53 | def main(input_file): 54 | garbage_info = [] 55 | # Read file with nuclei scan and append to the list 56 | with open(input_file, 'r') as file: 57 | scan_results = [result.strip() for result in file.readlines()] 58 | 59 | for i in scan_results: 60 | # Check if there is garbage information in [INF] brackets 61 | if i.split(" ")[0] == '[INF]': 62 | # Append it to "garbage_info" list and remove from original one 63 | garbage_info.append('Garbage: ' + i) 64 | scan_results.remove(i) 65 | print(garbage_info) 66 | else: 67 | continue 68 | 69 | # Identify the color of each severity 70 | color_map = { 71 | '[critical]': Fore.MAGENTA, 72 | '[high]': Fore.RED, 73 | '[medium]': Fore.YELLOW, 74 | '[low]': Fore.GREEN, 75 | '[info]': Fore.CYAN, 76 | '[unknown]': Fore.WHITE, 77 | '[]': Fore.WHITE 78 | } 79 | 80 | # Sort by severity and URL and split the list 81 | results = [i.split(" ") for i in sort_by_all(scan_results)] 82 | 83 | # Move [severity] to the first position, color with color_map and remove from original position 84 | for i in results: 85 | print(color_map.get(i[2]) + i[2] + Style.RESET_ALL + " " + " ".join(i).replace(i[2], '')) 86 | 87 | 88 | if __name__ == '__main__': 89 | # Colorama function to color text in CMD 90 | init() 91 | # Available arguments(in CMD type -h for help) 92 | parser = ArgumentParser() 93 | parser.add_argument('-i', '--input', help='Input file with Nuclei scan: /home/kali/scan.txt', default='', required=True) 94 | args = parser.parse_args() 95 | 96 | # Check if file or path is valid and sort it 97 | if args.input != '': 98 | try: 99 | main(args.input) 100 | except FileNotFoundError: 101 | print(Fore.RED + '[!] Not existing file or path' + Style.RESET_ALL) 102 | else: 103 | print(Fore.RED + '[!] No input file was given' + Style.RESET_ALL) 104 | sys.exit(1) 105 | 106 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | colorama 2 | --------------------------------------------------------------------------------