├── README.md ├── requirements.txt └── scscanner.py /README.md: -------------------------------------------------------------------------------- 1 | # scscanner_py 2 | scscanner_py is tool to read website status code response from the lists. This tool is reworked from bash version of [scscanner](https://github.com/yuyudhn/scscanner). 3 | 4 | ## Requirements 5 | - requests 6 | - urllib3 7 | - datetime 8 | - argparse 9 | 10 | Tested on **Debian** with **Python 3.10.8** 11 | 12 | ## Features 13 | - Multi-threading for fast scanning. 14 | - Filter status code from target list. 15 | - Save to file option. 16 | 17 | ## How to use 18 | Help menu. 19 | ```bash 20 | ➜ scscanner_py git:(main) ✗ python3 scscanner.py 21 | 22 | ___ ___ ___ ___ __ _ _ __ _ __ ___ _ __ 23 | / __|/ __/ __|/ __/ _` | '_ \| '_ \ / _ \ '__| 24 | \__ \ (__\__ \ (_| (_| | | | | | | | __/ | 25 | |___/\___|___/\___\__,_|_| |_|_| |_|\___|_| 26 | scscanner - Massive HTTP Status Code Scanner 27 | 28 | usage: scscanner.py [-h] [-T list.txt] [-w [15]] [-t google.com] [-f 200] [-s] [-o result.txt] 29 | 30 | options: 31 | -h, --help show this help message and exit 32 | -T list.txt File contain lists of domain 33 | -w [15], --workers [15] 34 | Thread value. Default value is 4 35 | -t google.com, --target google.com 36 | Single domain check 37 | -f 200, --filter 200 Status code filter 38 | -s, --silent Silent mode option. Don't print status code output 39 | -o result.txt, --output result.txt 40 | Save the results to file 41 | 42 | ``` 43 | Scan domain lists. 44 | ``` 45 | python3 scscanner.py -T lists.txt --workers 20 46 | ``` 47 | Scan single domain. 48 | ``` 49 | python3 scscanner.py -t https://blog.linuxsec.org 50 | ``` 51 | Scan domain list with status code filtering. 52 | **Example**: filter only '200' response. 53 | ``` 54 | python3 scscanner.py -T lists.txt -w 20 -f 200 55 | ``` 56 | Silent option, just print url with match status code filter. 57 | ``` 58 | python3 scscanner.py -T lists.txt -s --filter 200 --workers 20 59 | ``` 60 | With save to file options. 61 | ```bash 62 | python3 scscanner.py -T list.txt --workers 20 --output asuka.txt 63 | ``` 64 | ## Screenshot 65 | ![scscanner](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg8vq_xnWyaZT-RB5gbdbsuiI7yd5DtDXlsNr2J51htqvtOkWc92y_TA9TF73t4fb0lUoq7srKOKwnKrPdlZmbx5ZCLeW3zeO_yE-cuOTE1hNLgpd2Al9uraODHv_0pv1H6-pG7oeHZi3WhvBBWgBPqTpa4AYCYbBLllNnVKGzdW4OLvD__5jrHL7Tzcw/s917/scscanner.png "scscanner") 66 | 67 | ## Disclaimer 68 | I am just learning **ThreadPoolExecutor** so maybe this tool is dirty implementation of python threading. Feel free to contribute for better code quality. 69 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | urllib3 3 | datetime 4 | argparse 5 | -------------------------------------------------------------------------------- /scscanner.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | from concurrent.futures import ThreadPoolExecutor, as_completed 3 | import requests 4 | from requests import RequestException 5 | import urllib3 6 | import sys 7 | import argparse 8 | import datetime as dt 9 | import codecs 10 | import os 11 | 12 | urllib3.disable_warnings() 13 | 14 | class color: 15 | purple = '\033[95m' 16 | cyan = '\033[96m' 17 | darkcyan = '\033[36m' 18 | blue = '\033[94m' 19 | green = '\033[92m' 20 | yellow = '\033[93m' 21 | red = '\033[91m' 22 | bold = '\033[1m' 23 | underline = '\033[4m' 24 | reset = '\033[0m' 25 | magenta = "\033[35m" 26 | 27 | parser = argparse.ArgumentParser() 28 | parser.add_argument('-T', metavar='list.txt', type=str, help='File contain lists of domain') 29 | parser.add_argument('-w', '--workers', metavar='15', nargs='?', default=4, type=int, help='Thread value. Default value is 4') 30 | parser.add_argument("-t", "--target", metavar='google.com', type=str, help='Single domain check') 31 | parser.add_argument("-f", "--filter", metavar='200', type=int, help='Status code filter') 32 | parser.add_argument("-s", "--silent", default=False, action="store_true", help="Silent mode option. Don't print status code output") 33 | parser.add_argument("-o", "--output", metavar='result.txt', type=str, help='Save the results to file') 34 | args = parser.parse_args() 35 | domainlist = args.T 36 | worker = args.workers 37 | singledomain = args.target 38 | statuscodefilter = args.filter 39 | silentopts = args.silent 40 | output_file = args.output 41 | 42 | today = dt.datetime.now() 43 | dateonly = today.date() 44 | headers = { 45 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36' 46 | } 47 | path = os.getcwd() 48 | dir_name = ("scscanner", str(dateonly)) 49 | created_dirname = "-".join(dir_name) 50 | 51 | def argscheck(): 52 | if len(sys.argv)==1: 53 | parser.print_help(sys.stderr) 54 | sys.exit(1) 55 | elif domainlist == None and singledomain == None: 56 | parser.print_help(sys.stderr) 57 | print() 58 | print(f"{color.red}{color.bold}Error: Domain list or target is mandatory{color.reset}{color.reset}") 59 | sys.exit(1) 60 | elif domainlist is not None and singledomain is not None: 61 | parser.print_help(sys.stderr) 62 | print() 63 | print(f"{color.red}{color.bold}Error: Please chose either single target or bulk target.{color.reset}{color.reset}") 64 | sys.exit(1) 65 | elif silentopts and statuscodefilter is None: 66 | parser.print_help(sys.stderr) 67 | print() 68 | print(f"{color.red}{color.bold}Error: -s only work if -f is supplied.{color.reset}{color.reset}") 69 | sys.exit(1) 70 | elif domainlist is not None: 71 | try: 72 | codecs.open(domainlist, encoding="utf-8", errors="strict").readlines() 73 | except Exception as err: 74 | print(f"{color.red}{color.bold}Error: {type(err).__name__} was raised. Please provide valid domain list{color.reset}{color.reset}") 75 | sys.exit(1) 76 | 77 | 78 | def banner(): 79 | print(""" 80 | ___ ___ ___ ___ __ _ _ __ _ __ ___ _ __ 81 | / __|/ __/ __|/ __/ _` | '_ \| '_ \ / _ \ '__| 82 | \__ \ (__\__ \ (_| (_| | | | | | | | __/ | 83 | |___/\___|___/\___\__,_|_| |_|_| |_|\___|_| 84 | scscanner - Massive HTTP Status Code Scanner 85 | """) 86 | if not silentopts: 87 | banner() 88 | else: 89 | pass 90 | 91 | def domaincheck(probed): 92 | if not probed.startswith("http://") and not probed.startswith("https://"): 93 | probed = 'http://' + probed 94 | else: 95 | probed = probed 96 | return requests.get(probed, headers=headers, allow_redirects=False, verify=False, timeout=7) 97 | 98 | def savedresult(httpcode, domain): 99 | try: 100 | if output_file: 101 | file_name = (httpcode, output_file) 102 | created_filename = "-".join(file_name) 103 | final_dir = os.path.join(path, created_dirname, created_filename) 104 | os.makedirs(os.path.dirname(final_dir), exist_ok=True) 105 | with open(final_dir, "a") as f: 106 | f.write(domain + '\n') 107 | f.close() 108 | except Exception as err: 109 | return (f"{type(err).__name__} was raised: {err}") 110 | 111 | class scscanner: 112 | def statuscode(probed): 113 | try: 114 | req = domaincheck(probed) 115 | if not statuscodefilter: 116 | savedresult(str(req.status_code), probed) 117 | if statuscodefilter: 118 | if req.status_code == statuscodefilter: 119 | savedresult(str(req.status_code), probed) 120 | if silentopts: 121 | return(probed) 122 | if (statuscodefilter == 301) or (statuscodefilter == 302): 123 | return(f"[{color.bold}{req.status_code}{color.reset}] - {probed} --> {req.headers['Location']}") 124 | return(f"[{color.bold}{req.status_code}{color.reset}] - {probed}") 125 | else: 126 | pass 127 | else: 128 | if req.status_code == 200: 129 | return(f"[{color.green}{req.status_code}{color.reset}] - {probed}") 130 | elif (req.status_code == 301) or (req.status_code == 302): 131 | return(f"[{color.yellow}{req.status_code}{color.reset}] - {probed} --> {color.yellow}{req.headers['Location']}{color.reset}") 132 | else: 133 | return(f"[{color.red}{req.status_code}{color.reset}] - {probed}") 134 | except RequestException as err: 135 | if statuscodefilter: 136 | pass 137 | else: 138 | savedresult(str("000"), probed) 139 | return(f"[000] - {probed} [{color.bold}{color.red}{type(err).__name__}{color.reset}]") 140 | 141 | def singlescan(): 142 | probed = singledomain 143 | statusresult = scscanner.statuscode(probed) 144 | if statusresult is not None: 145 | print(statusresult) 146 | else: 147 | print(f"{color.bold}Domain status code and status code filter is not match{color.reset}") 148 | 149 | def masscan(): 150 | with ThreadPoolExecutor(max_workers=worker) as executor: 151 | with codecs.open(domainlist, encoding="utf-8", errors="strict") as tglist: 152 | domainname = tglist.read().splitlines() 153 | loopcheck = [executor.submit(scscanner.statuscode, probed) for probed in domainname] 154 | try: 155 | for future in as_completed(loopcheck): 156 | if future.result(): 157 | print(future.result()) 158 | else: 159 | pass 160 | except KeyboardInterrupt as err: 161 | tglist.close() 162 | print(f"{type(err).__name__}") 163 | os._exit(1) 164 | finally: 165 | executor.shutdown() 166 | 167 | if __name__ == '__main__': 168 | argscheck() 169 | try: 170 | if output_file and not silentopts: 171 | print(f"{color.bold}Your result will be saved at: {color.green}{os.path.join(path, created_dirname)}{color.reset}\n") 172 | for _ in [0]: 173 | if singledomain: 174 | scscanner.singlescan() 175 | else: 176 | scscanner.masscan() 177 | except Exception as err: 178 | print(f"{type(err).__name__} was raised: {err}") 179 | finally: 180 | sys.exit() --------------------------------------------------------------------------------