diff --git a/README.md b/README.md index 35110c3..09b8e3d 100644 --- a/README.md +++ b/README.md @@ -9,22 +9,33 @@ o888o `8. o88' 888o .8' -* Crawl URL from GHDB. -* Let user defined and saved dorks as per severity/category so dorks can be quickly executed to gather intel -* Generate Report and JSON file of extracted urls -* Require python3 +* Build on python3 +* Fetch exposed URLs using google dorks. +* Support HTML Report to feed results to crawler. +* Support JSON & XML output of extracted urls to feed results to external apis. +* Support grep-able output generation. +* Suport proxies. +* Multithread & optimised for better performance. +* Easy dorks updation. +* Uses huge dork collection. +* Dorks db is updating frequently. +* Easily integrable with other tools. +* Can be easily optimized to generate low noise and fetch quality results. +* Can be used for checking leaked info for tagreted domain. +* Integrate in ecosystem to search for urls for targeted domain and check for any blacklisted/unwanted exposed info. +* Out of the box search queries can be easily extendable to check extra information. ## usage - f0x.py [-h] [-s SITE] [-q QUERY] [-i] [-A EX_QUERY] [-C CATEGORY] [-S {1,2,3,4,5,6,7,8,9,10}] - [--only] [--upper] [-a] [-Q] [-r PAGE_SIZE] [-t DORK_SIZE] [-T MAX_RESULTS] [-m MIN] [-M MAX] - [-d DELAY] [-p PARALLEL] [-U UA] [-o OUTPUT] [-j] [-R] [--update] [-L] [-v] + $f0x.py [-h] [-d DOMAIN] [-q QUERY] [-n] [-Q EX_QUERY] [-c CATEGORY] [-cA] [-S SEVERITY] [-SQ] [-SA] [-t THREADS] [-p PROXY] + [-pF PROXY_FILE] [-pO] [-pC PROXY_COUNT] [-C PROXY_CONN] [--no-ssl-check] [--timeout TIME_OUT] [-m DELAY_MIN] + [-M DELAY_MAX] [-w DELAY] [-U UA] [--update] [-v] [-V] [-r PAGE_SIZE] [-R NO_OF_PAGES] [-T MAX_RESULTS] [-l] [-L] + [-o OUT_DIR] [-oJ] [-oX] [-oR] [--silent] ## example $ python3 f0x.py --update - $ python3 f0x.py --L - $ python3 f0x.py -C 'Files_Containing_Juicy_Info' -o /ghdb/juicyfiles -T 60 -v - $ python3 f0x.py -S 9 -o /ghdb/juicyfiles -T 60 -v + $ python3 f0x.py -L + $ python3 f0x.py --any --quality -v -p "http://10.10.10.10:4444" --no-ssl-check -t 3 diff --git a/f0x.config b/f0x.config new file mode 100644 index 0000000..f1799e7 --- /dev/null +++ b/f0x.config @@ -0,0 +1,4 @@ +[defaults] + +repo_url=https://github.com/dineshsaini/dorks.git +dork_path=~/.f0x/dorks diff --git a/f0x.py b/f0x.py index 2faa190..ddbba99 100755 --- a/f0x.py +++ b/f0x.py @@ -1,753 +1,982 @@ -#!/usr/bin/env pyton3 +#!/usr/bin/env python3 # -*- coding: utf-8 -*- +__NAME__ = "f0x.py" +__VERSION__ = "2.0" + import argparse import re import os -import errno -import urllib.parse as urlparse -import random import time -import mechanize -import requests import json +import threading +import asyncio +import random +from proxybroker import Broker +from concurrent.futures import ThreadPoolExecutor from git import Repo -import shutil + +from lib.prettyPrint import Prettify as pp +from lib.config import ConfigManager as conf +from lib.utils import DirUtil as dutil +from lib.utils import FileUtil as futil +from lib.google import GoogleSearch as gsrch +from lib.utils import Random as rand +from lib.useragents import UA + def banner(): - print (''' - .o88o. .o o. - 888 `" .8' `8. - o888oo .8' oooo ooo `8. - 888 88 `88b..8P' 88 - 888 88 Y888' 88 - 888 `8. .o8"'88b .8' - o888o `8. o88' 888o .8' - - '''); + print(pp.green(' .o88o. .o o.')) + print(pp.green(' 888 `" .8\' `8.')) + print(pp.green(' o888oo .8\' ' + pp.yellow('oooo ooo') + ' `8.')) + print(pp.green(' 888 88 ' + pp.yellow('`88b..8P') + ' 88')) + print(pp.green(' 888 88 ' + pp.yellow('Y888') + ' 88')) + print(pp.green(' 888 `8. ' + pp.yellow('.o8"\'88b') + ' .8\'')) + print(pp.green(' o888o `8. ' + pp.yellow('o88\' 888o') + ' .8\'')) + print() + parser = argparse.ArgumentParser() -parser.add_argument('-s', '--site', help='Specify target site.', dest='site') +# input +parser.add_argument('-d', '--domain', help='Specify Target domain.', + dest='domain') + +parser.add_argument('-q', '--query', help='Specify query/dork manually, and' + + ' don\'t use more dorks from dorks-db.', dest='query') + +parser.add_argument('-n', '--no-stop', help='Works with `--query`, If ' + + 'specified dorks from dorks-db will also be used along ' + + 'with manually supplied dork.', dest='query_nostop', + action="store_true") + +parser.add_argument('-Q', '--query-args', help='Specify extra query to ' + + 'supply with each dorks.', dest='ex_query') + +# dork selection +parser.add_argument('-c', '--category', help='Comma (,) separated dorks ' + + 'categories to use.', dest='category') + +parser.add_argument('-cA', '--any', help='Use all available categories.', + dest='cat_any', action="store_true") + +parser.add_argument('-S', '--severity', help='Comma (,) separated severity ' + + 'range(from 1 to 10). eg. consider range expansion as : ' + + '"-4" will become "1,2,3,4", or ' + + '"2-5" will become "2,3,4,5", or ' + + '"7-" will become "7,8,9,10", or ' + + '"1-4,7" will become "1,2,3,4,7".', dest='severity') + +parser.add_argument('-SQ', '--quality', help='Only use quality dorks ' + + 'i.e. with `severity >= 7`.', dest='sev_quality', + action="store_true") + +parser.add_argument('-SA', '--all', help='Use all available severities.', + dest='sev_all', action="store_true") + +# optimize fox +parser.add_argument('-t', '--threads', help='Max parallel threads ' + + '(Default: 3).', type=int, dest='threads') + +parser.add_argument('-p', '--proxy', help="Specify proxy to use.", dest='proxy') + +parser.add_argument('-pF', '--proxy-file', help='Specify file to read proxies ' + +'list (one per line).', dest='proxy_file') + +parser.add_argument('-pO', '--open-proxies', help='Make use of open public ' + + 'proxies.', dest='proxy_open', action='store_true') + +parser.add_argument('-pC', '--open-proxies-count', help='Try collecting ' + + '`n` open proxies. (Default: 20)', dest='proxy_count', + type=int) + +parser.add_argument('-C', '--conn', help='Max connections per proxy ' + + '(Default: 2).', dest='proxy_conn', type=int) + +parser.add_argument('--no-ssl-check', help='Disable certificate check.', + dest='no_ssl_check', action='store_true') + +parser.add_argument('--timeout', help='Set request timeout.', dest='time_out', + type=int) + +parser.add_argument('-m', '--min-delay', help='Specify minimum delay(sec) ' + + 'between requests. (Default: `max-delay` - 3)s.', + dest='delay_min', type=int) + +parser.add_argument('-M', '--max-delay', help='Specify maximum delay(sec) ' + + 'between requests. (Default: `min-delay` + 3)s.', + dest='delay_max', type=int) + +parser.add_argument('-w', '--wait', help='Specify fix delay(sec) between ' + + 'requests (Default: 1s).', dest='delay', type=int) -parser.add_argument('-q', '--query', help='Dork to use. If specified, other files will not be read.', dest='query') -parser.add_argument('-i', '--inclusive', help='This works with `query` option only, if used, will also read dorks from file. ', dest='inc', action="store_true") -parser.add_argument('-A', '--args', help='Specify extra query to supply with each dorks.', dest='ex_query') +parser.add_argument('-U', '--user-agent', help='Specify User Agent.', dest='ua') -parser.add_argument('-C', '--category', help='Use dorks from this category only.', dest='category') -parser.add_argument('-S', '--severity', help='Specify minimum severity(inclusive) dork file to read, range is [0, 10], defalut: 5.', dest='severity', type=int, choices=range(1, 11)) -parser.add_argument( '--only', help='Use along with severity, to select only a particular value.', dest='s_only', action='store_true') -parser.add_argument( '--upper', help='Use along with severity, to mark provided value as upper limit (exclusive).', dest='s_upper', action='store_true') -parser.add_argument('-a', '--all', help='Use all the dork files to fetch result (overrides --only, --upper flags).', dest='s_all', action='store_true') -parser.add_argument('-Q', '--quality', help='Use only top severity(>=8) dork files (overrides --only, --upper flags). ', dest='s_qual', action='store_true') +parser.add_argument('--update', help='Update dorks repo and exit.', + dest='repo_update', action="store_true") -parser.add_argument('-r', '--results', help='Total results to fetch in one request, default is 30.', dest='page_size', type=int) -parser.add_argument('-t', '--total', help='Total results to fetch for each dork, default is 150.', dest='dork_size', type=int) -parser.add_argument('-T', '--max', help='Maximum results to fetch for all the dorks combined.', dest='max_results', type=int) +parser.add_argument('-v', '--verbose', help='Be verbose.', dest='verbose', + action="store_true") -#parser.add_argument('-P', '--proxy', help='proxy', dest='') -#parser.add_argument('-f', '--proxy-file', help='list of proxies', dest='') -#parser.add_argument('-c', '--conn', help='connections per proxy', dest='') +parser.add_argument('-V', '--version', help='Display version info and exit.', + dest='version', action="store_true") -parser.add_argument('-m', '--mintime', help='Specify minimum sec to wait between requests, If not specified, default 5 sec range is assumed', dest='min', type=int) -parser.add_argument('-M', '--maxtime', help='Specify maximum sec to wait between requests, if not specified, default 5 sec range is assumed.', dest='max', type=int) -parser.add_argument('-d', '--delay', help='Specify fix delay(in sec), if specified, took priority over variable delay.', dest='delay', type=int) -parser.add_argument('-p', '--parallel', help='Specify total no of parallel requests, default is 5.', dest='parallel', type=int) -parser.add_argument('-U', '--user-agent', help='Specify User Agent ', dest='UA') +parser.add_argument('-r', '--results', help='Dork results to fetch in one ' + + 'page request (Default: 30).', dest='page_size', type=int) -parser.add_argument('-o', '--output', help='Specify output directory', dest='output') -parser.add_argument('-j', '--json', help='Save output in JSON format only', dest='json', action="store_true") -parser.add_argument('-R', '--report', help='Create Report along with JSON format ouput, default', dest='report', action="store_true") +parser.add_argument('-R', '--requests', help='Pages to request for each ' + + 'dork (Default: 5).', dest='no_of_pages', type=int) -parser.add_argument('--update', help='Update Dorks Repo, and exit', dest='updaterepo', action="store_true") -parser.add_argument('-L', '--list', help='List Repo categories, total dorks and exit', dest='listrepo', action="store_true") -parser.add_argument('-v', '--verbose', help='Be verbose.', dest='verbose', action="store_true") +parser.add_argument('-T', '--max-results', help='Maximum results to fetch ' + + 'for all the dorks combined.', dest='max_results', type=int) + +# output filters +parser.add_argument('-l', '--list-dorks', help='List all dorks to be used ' + + 'and exit. Specify `category` or `severity` to narrow ' + + 'down the list. ', dest='list_dorks', action="store_true") + +parser.add_argument('-L', '--categories', help='List available categories ' + + 'and exit.', dest='list_cat', action="store_true") + +parser.add_argument('-o', '--outdir', help='Specify output directory.', + dest='out_dir') + +parser.add_argument('-oJ', '--out-json', help='Save output in JSON format.', + dest='out_fmt_json', action="store_true") + +parser.add_argument('-oX', '--out-xml', help='Save output in XML format.', + dest='out_fmt_xml', action="store_true") + +parser.add_argument('-oR', '--out-report', help='Create html report with ' + + 'JSON format results.', dest='out_report', + action="store_true") + +parser.add_argument('--silent', help='Do not print fetched links to stdout, ' + + 'just save them in file.', dest='out_silent', + action='store_true') args = parser.parse_args() +if args.version: + print("{} v{}".format(pp.as_bold(pp.red(__NAME__)), pp.blue(__VERSION__))) + quit() + banner() -if not (args.site or \ - args.query or \ - args.category or \ - args.severity or \ - args.s_all or \ - args.s_qual or \ - args.page_size or \ - args.dork_size or \ - args.max_results or \ - args.min or \ - args.max or \ - args.delay or \ - args.parallel or \ - args.UA or \ - args.output or \ - args.updaterepo or \ - args.listrepo): - print ("[ERROR]: no options are specified\n\n") - print (parser.format_help()) - quit() -#---------------------------------------------------------------------------------- +class F0x: + + def __init__(self, verbose=False): + self._conn_per_proxy_count = None + self._proxy_ptr = -1 + self._out_fmt_json = 1 << 0 + self._out_fmt_xml = 1 << 1 + self._out_report = 1 << 2 + self._outmode = 0 + self._proxy_lock = threading.Lock() + self._count_lock = threading.Lock() + + self.verbose = verbose + self.severities = None + self.categories = None + self.domain = None + self.query = None + self.ex_args = None + self.process_dorksdb_flag = True + self.useragent = None + self.threads = 3 + self.delay_min = 1 + self.delay_max = 1 + self.connection_per_proxy = 2 + self.proxies_list = None + self.request_timeout = None + self.ssl_check = True + self.open_proxy_count = 20 + self.page_size = gsrch.correct_page_size(30) + self.no_of_pages = 5 + self.max_results = None + self.outdir = None + self.out_silent = False + self.results_count = 0 + + self._load_conf() -#read config file -def get_value(key): - with open(getFileName(os.path.dirname(os.path.realpath(__file__)), 'f0x.config'), 'r') as config: - for line in config: - if line.startswith(key): - return line.split('=')[1].strip('\n') + def _load_conf(self): + conf.load('./f0x.config') + if self.is_verbose(): + pp.p_debug("Loaded Config file, keys: {}" + .format(self.get_conf().getKeys())) + + def get_conf(self): + return conf.getConfig() -def getNewDir(o, dn=''): - out_dir = o - if out_dir.endswith('/'): - out_dir += dn - else: - out_dir += '/' + dn + def is_verbose(self): + return self.verbose + + def set_categories(self, categories=None): + self.categories = categories + + def get_categories(self): + return self.categories + + def set_severities(self, severities=None): + self.severities = severities + + def get_severities(self): + return self.severities + + def set_domain(self, domain): + self.domain = domain + + def get_domain(self): + return self.domain + + def set_query(self, query): + self.query = query + + def get_query(self): + return self.query + + def set_ex_args(self, ex_args): + self.ex_args = ex_args + + def get_ex_args(self): + return self.ex_args + + def set_useragent(self, useragent): + self.useragent = useragent + + def get_useragent(self): + return self.useragent + + def set_process_dorksdb_flag(self, flag): + self.process_dorksdb_flag = flag + + def get_process_dorksdb_flag(self): + return self.process_dorksdb_flag + + def set_threads(self, threads): + self.threads = threads + + def get_threads(self): + return self.threads + + def set_delay_min(self, delay_min): + self.delay_min = delay_min + + def get_delay_min(self): + return self.delay_min + + def set_delay_max(self, delay_max): + self.delay_max = delay_max + + def get_delay_max(self): + return self.delay_max + + def set_connection_per_proxy(self, conn): + self.connection_per_proxy = conn + + def get_connection_per_proxy(self): + return self.connection_per_proxy + + def add_proxy(self, proxy): + if not self.proxies_list: + self.proxies_list = [] + self._conn_per_proxy_count = [] + + self.proxies_list += [proxy] + self._conn_per_proxy_count += [0] + + def get_proxy_list(self): + return self.proxies_list + + def set_page_size(self, page_size): + self.page_size = page_size + + def get_page_size(self): + return self.page_size + + def set_no_of_pages(self, no_of_pages): + self.no_of_pages = no_of_pages + + def get_no_of_pages(self): + return self.no_of_pages + + def set_max_results(self, max_results): + self.max_results = max_results + + def get_max_results(self): + return self.max_results + + def set_outdir(self, outdir): + self.outdir = outdir + + def get_outdir(self): + return self.outdir + + def set_outmode_json(self): + self._outmode |= self._out_fmt_json + + def set_outmode_xml(self): + self._outmode |= self._out_fmt_xml + + def set_outmode_report(self): + self._outmode |= self._out_report + + def get_outmode_json(self): + return (self._outmode & self._out_fmt_json) == self._out_fmt_json + + def get_outmode_xml(self): + return (self._outmode & self._out_fmt_xml) == self._out_fmt_xml + + def get_outmode_report(self): + return (self._outmode & self._out_report) == self._out_report + + def set_output_silent(self, flag): + self.out_silent = flag + + def is_output_silent(self): + return self.out_silent + + def set_results_count(self, count): + self.results_count = count + + def get_results_count(self): + return self.results_count + + def set_request_timeout(self, timeout): + self.request_timeout = timeout + + def get_request_timeout(self): + return self.request_timeout + + def set_ssl_check(self, ssl_check): + self.ssl_check = ssl_check + + def do_ssl_check(self): + return self.ssl_check + + def set_open_proxy_count(self, count): + self.open_proxy_count = count + + def get_open_proxy_count(self): + return self.open_proxy_count + + async def _record_proxy(self, proxies): + while True: + proxy = await proxies.get() + if proxy is None: + break + + proto = 'https' if 'HTTPS' in proxy.types else 'http' + proxy_url = '%s://%s:%d' % (proto, proxy.host, proxy.port) + self.add_proxy(proxy_url) + + if fox.is_verbose(): + pp.p_log("Found proxy: {}".format(pp.light_green(proxy_url))) + + def collect_open_proxies(self): + proxies = asyncio.Queue() + broker = Broker(proxies) + tasks = asyncio.gather(broker.find(types=['HTTP', 'HTTPS'], + limit=self.get_open_proxy_count()), + self._record_proxy(proxies)) + + loop = asyncio.get_event_loop() + loop.run_until_complete(tasks) + + def update_dorks_repo(self): + pp.p_log("Building Dork Repo.") + repo_url = self.get_conf().get('repo_url') + pp.p_log("Fetching from '{}'".format(repo_url)) + + tmpdir = dutil.create_temp_dir('f0x', 'repo_') + Repo.clone_from(repo_url, tmpdir) + pp.p_log("Done Fetching.") + + rmdirs = ['.git'] + rmfiles = ['README.md', 'LICENSE'] + + for i in rmdirs: + try: + g = dutil.get_dir(tmpdir, i) + except: + pass + else: + dutil.rmdir(g) + + for i in rmfiles: + try: + f = futil.get_file(tmpdir, i) + except: + pass + else: + os.remove(f) + try: + dutil.merge_dirs(tmpdir, self.get_dork_path()) + except Exception as e: + pp.p_error(e) + quit() + + pp.p_log("Dork Repo updated.") + + def get_dork_path(self): + dp = '' + flag = False - if not os.path.exists(out_dir): try: - os.makedirs(out_dir) - except OSError as e: - if e.errno != errno.EEXIST: - raise - return out_dir - -def getDir(o, dn = ''): - return getNewDir(o, dn) - -def query_encode(query): - return urlparse.quote_plus(query) - -# query, site, extra_query_string -def createURL(q, s, eqs): - u = 'https://www.google.com/search?gbv=1&q=' - if q == '': - print ("Query cannot be empty") - return - u += query_encode(q) - if eqs != '': - u += '+' + query_encode(eqs) - if s != '': - u += '+' + query_encode(s) - u += '&btnG=Google+Search' - return u - -def getSeverities(): - sev = [] - if severity_flag == 0: - sev = list (range (severity, 11)) - elif severity_flag == 1: - sev = [severity] - elif severity_flag == 2: - sev = list (range (1, severity)) #if severity = 1, return empty set - return sev - -def getFiles(f): - l = [] - for j in os.listdir(f): - t = f - if t.endswith('/'): - t += j + dp = self.get_conf().get('dork_path') + except: + pp.p_error("Dorks path not exists.") + flag = True else: - t += '/' + j + if dp == '': + pp.p_error("Dorks path not defined.") + flag = True + + if flag: + raise Exception("Error in Config file.") - if os.path.isfile(t): - l += [t] + if dp.startswith('~'): + dp = os.path.expanduser(dp) + elif dp.startswith('/'): + pass + elif dp.startswith('./'): + cwd = os.path.realpath('.') + dp = dutil.join_names(cwd, dp[2:]) else: - l += getFiles(t) - return l - -def getDirs(f): - l = [] - for j in os.listdir(f): - t = f - if t.endswith('/'): - t += j + cwd = os.path.realpath('.') + dp = dutil.join_names(cwd, dp) + + return dp + + def list_repo_categories(self): + dp = None + try: + dp = self.get_dork_path() + except Exception as e: + pp.p_error(e) + quit() + + dl = dutil.get_dir_list(dp, True) + if len(dl) == 0: + pp.p_log("No Dorks available, Update dork repo.") + return + + for i in dl: + dc = re.sub('^{}[/]?'.format(dp), '', i) + dc = re.sub('/', '.', dc) + td = len(dutil.get_files_list(i, True)) + pp.p_log("Category: {}".format(dc)) + pp.p_log("Total Dorks: {}\n".format(td), '**') + + def list_dorks(self): + cat = None + sev = None + + if self.get_categories(): + cat = self.get_categories() else: - t += '/' + j - - if os.path.isdir(t): - l += [t] - l += getDirs(t) - return l - -def getDorks(rq, inc, svr, cat): - dorks = [] - if rq: - if svr == 10: - dorks += [rq] - if not inc: + cat = [""] + + if self.get_severities(): + sev = self.get_severities() + else: + sev = range(1, 11) + + for c in cat: + for d in self.get_dorks(c, sev): + pp.p_log(d) + + def get_dorks(self, category, sev_list): + dorks = [] + dpath = None + chome = '' + + if not sev_list or len(sev_list) == 0: return dorks + + try: + dpath = self.get_dork_path() + except Exception as e: + pp.p_error(e) + return [] + + if category: + chome = re.sub('\.', '/', category) + + dpath = dutil.get_dir(dpath, chome) + + for i in dutil.get_files_list(dpath, True): + with open (i, 'r') as dfile: + d = None + j = None + for l in dfile: + if l.lstrip().lower().startswith('googledork:'): + d = re.sub('^googledork:', '', l.lstrip().lower()) + d = d.strip() + elif l.lstrip().lower().startswith('severity:'): + j = re.sub('^severity:', '', l.lstrip().lower()) + j = j.strip() + elif (not d) and l.lstrip().lower().startswith('dork:'): + d = re.sub('^dork:', '', l.lstrip().lower()) + d = d.strip() + + if d and j: + break + + if j and int(j) in sev_list and d: + dorks.append(d) + + return dorks - dpath = get_value('dork_path') - chome = '' - - if cat != '': - chome = re.sub('\.', '/', cat) - - dpath = getDir(dpath, chome) - - for i in getFiles(dpath): - with open (i, 'r') as dfile: - d = '' - j = '' - for l in dfile: - if l.lstrip().lower().startswith('dork:'): - d = re.sub('^[dD][oO][rR][kK]:', '', l.lstrip()) - d = d.strip() - elif l.lstrip().lower().startswith('severity:'): - j = re.sub('^severity:', '', l.lstrip().lower()) - j = j.strip() - - if int(j) == svr: - dorks.append(d) - - return dorks - -def getUserAgents(): - uaf = get_value('useragents') - if not uaf.startswith('/'): #relative path - uaf = getFileName(os.path.dirname(os.path.realpath(__file__)), uaf) - - useragents = [] - with open(uaf, 'r') as uas: - useragents = [ua.strip('\n') for ua in uas] - return useragents - - -def wget(u): - hdrs = { - 'Host': 'www.google.com', - 'User-Agent': random.choice(useragents), - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'Accept-Language': 'en-US,en;q=0.5', - 'Accept-Encoding': 'gzip, deflate', - 'Referer': 'https://www.google.com/', - 'DNT': '1', - 'Connection': 'keep-alive', - 'Upgrade-Insecure-Requests': '1', - 'Cache-Control': 'max-age=0, no-cache', - 'Pragma': 'no-cache', - 'TE': 'Trailers' - } - req = requests.get(u, headers=hdrs) - return req.text - -def getNewRandomDir(o, dn=''): - return getNewDir(getNewDir(o, dn), str(time.time_ns())) - -def getFileName(o, n): - if o.endswith('/'): - return o + n - else: - return o + '/' + n - -def persist(r, d, s, o, fc): - if fc == 0: - fd = open(getFileName(o, 'dork.info'), 'w') - fd.write("dork: {}\n".format(d)) - fd.write("severity: {}\n".format(s)) - fd.close() - fd = open(getFileName(o, 'dork_' + str(fc + 1)), 'w') - fd.write(r) - fd.close() - -def getDelay(): - return delay + (random.random() * var_delay) - -def pageHasMoreResults(r): - o = re.search('aria-label="Next page"[^>]*>((Next >)|(]*>>))', r, re.I) - if o: - return True - else: - return False - -mr_achived = 0 -def canFetchMore(): - return (max_results - mr_achived) > 0 - -# TODO: make it synchronised later -def updateResultsCount(c): - global mr_achived - mr_achived += c - -# TODO: make it async later -def extractURLs(o, res): - fd = open(getFileName(o, 'urls.txt'), 'a') - - for pat in re.findall('\s+href="/url\?q=([^"&]*)[^"]*"[^>]*>', res, re.M|re.I): - if not re.search('^http(s)?://(www\.)?[^.]*\.google\.com', pat, re.I): - u = urlparse.unquote(pat) - fd.write("{}\n".format(u)) - - fd.close() - -def processDork(d, s, qe, ps, ds, o, sev): - if not canFetchMore(): - return - - u = createURL(d, s, qe) - i = -1 - rFlag = True - dd = getNewRandomDir(o, 'dorks') - r = 0 - print("[*] Processing dork: {}".format(d)) - while rFlag and canFetchMore() and ((ps * (i + 1)) <= ds): - url = '' - i += 1 - if i == 0: - url = "{}&start=&num={}".format(u, ps) + def fetch_page_response(self, dork, pagenum, proxy): + gurl = gsrch.prepare_URL(dork, self.get_domain(), self.get_query(), + pagenum, self.get_page_size()) + + ua = None + if self.get_useragent(): + ua = self.get_useragent() else: - url = "{}&start={}&num={}".format(u, ps * i, ps) - t = getDelay() - print("[*] Sleeping for {} sec.".format(t)) - time.sleep(t) - print("[*] Processing now.") - print("[*] Next Page Request: {}".format(r + 1)) - response = wget(url) - print("[*] Got Response.") - persist(response, d, sev, dd, r) - r += 1 - updateResultsCount(ps) - rFlag = pageHasMoreResults(response) - extractURLs(dd, response) - -# TODO: implement thread -def dbBuilder(): - for s in getSeverities(): - for i in getDorks(r_query, inclusive, s, category): - processDork(i, site, query_extra, page_size, dork_size, out_dir, s) - print("[*] Finished fetching results.") - -def jsonBuilder(o): - for f in os.listdir(o): - i = getFileName(o, f) - - if os.path.isdir(i): - if os.path.isfile(getFileName(i, 'urls.txt')): - l = [] - s = '' - d = '' - - with open(getFileName(i, 'urls.txt'), 'r') as urls: - for u in urls: - l += [u.strip('\n')] - - with open(getFileName(i, 'dork.info'), 'r') as infos: - for line in infos: - if line.startswith('dork: '): - d = re.sub('dork: ', '', line) - d = d.strip('\n') - elif line.startswith('severity: '): - s = re.sub('severity: ', '', line) - s = s.strip('\n') - - fd = open(getFileName(i, 'result.json'), 'w') - - data = { - 'severity' : s, - 'dork' : d, - 'urls' : l - } - - fd.write(json.dumps(data)) - fd.close() - -def buildReportObj(dd): - data = [] - - for i in range(1, 11): - data += [{ - 'severity': i, - 'lists': [] - }] - - for d in os.listdir(dd): - f = getFileName(dd, d) - - if os.path.isdir(f): - if os.path.isfile(getFileName(f, 'result.json')): - jd = {} - - with open(getFileName(f, 'result.json'), 'r') as jfile: - jd = json.load(jfile) + ua = UA.get_random_ua() + + return gsrch.fetch(gurl, ua, proxy, self.get_request_timeout(), + self.do_ssl_check()) + + def save_links(self, dork, dname, pnum, links_list): + if not self.get_outdir(): + for l in links_list: + pp.p_log(l) + return dname + + append = True + if pnum == 1: + append = False + dname = dutil.create_random_dir(self.get_outdir(), dname) + dname = re.sub('^{}[/]?'.format(self.get_outdir()), '', dname) + + futil.dump_list(futil. + join_names(dutil.get_dir(self.get_outdir(), + dname), + "{}.info".format(dname)), + ["dork: {}".format(dork)], append) + + futil.dump_list(futil. + join_names(dutil.get_dir(self.get_outdir(), dname), + "{}.txt".format(dname)), links_list, append) + + if not self.is_output_silent(): + for l in links_list: + pp.p_log(l) - data[int(jd['severity']) - 1]['lists'] += [{ - 'dork': jd['dork'], - 'path': './dorks/' + d + '/result.json', - 'count': len(jd['urls']) - }] - return data - -def reportBuilder(o, d): - of = getFileName(o, 'report.html') - fd = open(of, 'w') - banner = ''' - -''' - - do = buildReportObj(getDir(o, d)) - - css= ''' - .banner{ - font-weight: 600; - } - - .banner-footer { - font-style: italic; - } - - .severity { - font-size: 2.4em; - } - - .label { - font-size: 1.4em; - } - - .label-value { - font-style: italic; - font-weight: 600; - } - - ''' - - fd.write("OSINT Report - [GHDB]".format(css)) - fd.write(banner) - - for i in do: - fd.write('

Severity {}

'.format(i['severity'], i['severity'])) - for j in i['lists']: - fd.write('

Dork Used: {}

'.format(j['dork'])) - fd.write('

URLs Retrived: {}

'.format(j['count'])) - fd.write('

JSON File: {}

'.format(j['path'], j['path'])) - fd.write('
') - fd.write("
") - - fd.write("") - fd.close() - -def getDorkRepoUrl(): - return get_value('repo_url') - -def listStats(): - dp = '' - try: - dp = get_value('dork_path') - except: - pass - if dp == '': - print("[ERROR]: No Dorks Available. Check Config file.") - return - - for i in getDirs(dp): - dc = re.sub('^{}[/]?'.format(dp), '', i) - dc = re.sub('/', '.', dc) - td = len (getFiles(i)) - print("[*] Category: {}".format(dc)) - print("[**] Total Dork: {}\n".format(td)) - -def mergedir(s, d): - for i in os.listdir(s): - if os.path.isfile(getFileName(s, i)): - shutil.move(getFileName(s,i), getFileName(d, i)) - else: - mergedir(getDir(s, i), getDir(d, i)) - -def pullDorksRepo(): - print("[*] Building Dork Repo") - print("[*] Fetching from '{}'".format(getDorkRepoUrl())) - - tmpdir = '/tmp/f0x/' - if os.path.exists(tmpdir): - try: - shutil.rmtree(tmpdir) - except: - tmpdir = getNewRandomDir(tmpdir) + return dname - Repo.clone_from(getDorkRepoUrl(), tmpdir) + def update_results_stats(self, c): + with self._count_lock: + self.set_results_count(self.get_results_count() + c) - print("[*] Fetching done.".format(getDorkRepoUrl())) + def can_fetch_more(self): + if self.get_max_results(): + return self.get_results_count() < self.get_max_results() - g = getDir(tmpdir, '.git') - if os.path.exists(g): + return True + + def get_proxy_object(self): + proxy = {'proxy': None, 'loc': None} + p = None + l = None + + if self.get_proxy_list(): + pl = len(self.get_proxy_list()) + c = 0 + + with self._proxy_lock: + while True: + c += 1 + self._proxy_ptr += 1 + if self._proxy_ptr == pl: + self._proxy_ptr = 0 + + if self._conn_per_proxy_count[self._proxy_ptr] < \ + self.get_connection_per_proxy(): + p = self.get_proxy_list()[self._proxy_ptr] + self._conn_per_proxy_count[self._proxy_ptr] += 1 + l = self._proxy_ptr + break + + if c >= pl: + c = 0 + time.sleep((self.get_delay_min() * + self.get_no_of_pages()) / 4) + + if p: + proxy = {'proxy': {'http': p, 'https': p}, 'loc': l} + return proxy + + def release_proxy(self, proxyobj): + l = proxyobj['loc'] try: - shutil.rmtree(g) + if (l or int(l) == 0) and self._conn_per_proxy_count[l] != 0: + self._conn_per_proxy_count[l] -= 1 except: pass - - r = getFileName(tmpdir, 'README.md') - if os.path.isfile(r): - os.remove(r) - - l = getFileName(tmpdir, 'LICENSE') - if os.path.isfile(l): - os.remove(l) - mergedir(tmpdir, getDir(get_value('dork_path'))) + def process_dork(self, dork): + if self.is_verbose(): + pp.p_debug("Processing dork: {}".format(dork)) + + dname = "dork{}".format(int(random.random() * 1000)) + proxy = self.get_proxy_object() - print("[*] Dork Repo Updated, dork location: {}".format(get_value('dork_path'))) + for p in range(1, self.get_no_of_pages() + 1): + if not self.can_fetch_more(): + break + + time.sleep(rand.rand_between(self.get_delay_min(), + self.get_delay_max())) + response = None + try: + response = self.fetch_page_response(dork, p, proxy['proxy']) + except Exception as e: + self.release_proxy(proxy) + gsrch.session_cleanup() + pp.p_error(e) + return + + if self.is_verbose(): + pp.p_debug("Fetched page : {}".format(p)) + + links = gsrch.extract_urls(response) + if self.is_verbose(): + pp.p_debug("Found {} url(s).".format(len(links))) + + dname = self.save_links(dork, dname, p, links) + + self.update_results_stats(len(links)) + + if not gsrch.has_next(response): + break + + self.release_proxy(proxy) + gsrch.session_cleanup() + + def execute(self): + dorks = [] + if self.get_query(): + dorks += [self.get_query()] + + if self.get_process_dorksdb_flag(): + cat = [] + if self.get_categories(): + cat = self.get_categories() + + for c in cat: + dorks += self.get_dorks(c, self.get_severities()) + + if self.is_verbose(): + pp.p_debug("{} dorks to fetch.".format(len(dorks))) + + with ThreadPoolExecutor(max_workers=self.get_threads()) as exec: + exec.map(self.process_dork, dorks) + + self.make_report() + + def make_report(self): + if not self.get_outdir(): + return + + if not (self.get_outmode_json() or self.get_outmode_report() or + self.get_outmode_xml()): + return + + fdr = None + if self.get_outmode_report(): + fdr = open(futil.join_names(self.get_outdir(), 'index.html') , 'w') + fdr.write(' f0x Report: links' + + '') + + for ddir in dutil.get_dir_list(self.get_outdir()): + dname = re.sub('^{}[/]?'.format(self.get_outdir()), '', ddir) -def buildConfFile(cpath): - r = '' - d = '' - u = '' - try: - r = get_value('repo_url') - except: - pass + links = futil.get_file_aslist( + futil.get_file(ddir, "{}.txt".format(dname))) + + if self.get_outmode_json(): + with open(futil.join_names(ddir, '{}.json'.format(dname)), + 'w') as fd: + fd.write(json.dumps({'urls': links})) + + try: + fdh = None + fdx = None + + if self.get_outmode_report(): + fdh = open(futil.join_names(ddir, '{}.html'.format(dname)), + 'w') + + if self.get_outmode_xml(): + fdx = open(futil.join_names(ddir, '{}.xml'.format(dname)), + 'w') + + except Exception as e: + pp.p_error(e) + else: + if fdh: + fdh.write(' dork urls' + + '') + if fdx: + fdx.write('') + + for link in links: + if fdh: + fdh.write('{}
'.format(link, link)) + + if fdx: + fdx.write('{}'.format(link)) + + if fdx: + fdx.write('
') + if fdh: + fdh.write('') + + if fdr: + fdr.write(('dork: {} urls ' + + 'fetched: {}
').format(dname, dname, dname, + len(links))) + + finally: + if fdh: + fdh.close() + if fdx: + fdx.close() + + if fdr: + fdr.write('') + fdr.close() - try: - d = get_value('dork_path') - except: - pass + +fox = F0x(verbose=args.verbose) - try: - u = get_value('useragents') - except: - pass +if args.repo_update: + fox.update_dorks_repo() + quit() - fd = open(cpath, 'w') - if r == '': - fd.write('repo_url={}\n'.format('https://github.com/em-corp/dorks.git')) - else: - fd.write('repo_url={}\n'.format(r)) - if d == '': - fd.write('dork_path={}\n'.format(getDir(os.path.expanduser('~'), '.f0x/dorks'))) - else: - fd.write('dork_path={}\n'.format(d)) - if u == '': - fd.write('useragents=./user-agents\n') - else: - fd.write('useragents={}\n'.format(u)) - fd.close() +if args.list_cat: + fox.list_repo_categories() + quit() -def configure(): - cpath = getFileName(os.path.dirname(os.path.realpath(__file__)), 'f0x.config') +flag_dork_selector = False + +if args.severity: + flag_dork_selector = True + s = [] + l = "1" + m = "10" + + if re.search("[^0-9,-]", args.severity): + pp.p_error("Severity value can only contains numbers or numeral " + + "range, separated by comma (,).") + quit() + + for i in args.severity.split(','): + j = i + if i.startswith('-'): + j = l + i + elif i.endswith('-'): + j = i + m + + k = j.split('-') + for x in range(int(k[0]), int(k[-1]) + 1): + s += [x] + + s = list(set(s)) + fox.set_severities(s) - if not os.path.isfile(cpath): - print ("[*] Creating Configuration file.") - buildConfFile(cpath) - print('[*] Done.') +if args.sev_all: + flag_dork_selector = True + + s = [] + for x in range(1, 11): + s += [x] + + fox.set_severities(s) - if get_value('repo_url') == '' or \ - get_value('dork_path') == '' or \ - get_value('useragents') == '': - print("[*] Error Reading Conf file.") - print("[*] Creating new Configuration file.") - buildConfFile(cpath) - print('[*] Done.') +if args.severity and args.sev_all and fox.is_verbose(): + pp.p_debug("Provided severity range is overridden by `all` switch.") -#---------------------------------------------------------------------------------- +if args.sev_quality: + flag_dork_selector = True + + s = [] + for x in range(7, 11): + s += [x] + fox.set_severities(s) + +if (args.severity or args.sev_all) and args.sev_quality and fox.is_verbose(): + pp.p_debug("Provided severity (range | `all` switch) is overridden by " + + "`quality` switch.") + +if args.category: + flag_dork_selector = True + fox.set_categories(args.category.split(',')) -verbose = args.verbose +if args.cat_any: + flag_dork_selector = True + fox.set_categories(["."]) -if args.listrepo: - listStats() - quit() +if args.category and args.cat_any and fox.is_verbose(): + pp.p_debug("Provided categories value is overridden by `any` switch.") -configure() +if args.list_dorks: + fox.list_dorks() + quit() + +if args.query: + flag_dork_selector = True + fox.set_query(args.query.strip()) + fox.set_process_dorksdb_flag(args.query_nostop) -if args.updaterepo: - pullDorksRepo() +if not flag_dork_selector: + pp.p_error('Please provide atleast one dork selector from ' + + '`category`, `severity` or `query`.') quit() -site='' -# if site provided -if args.site: - site = args.site.strip() - site = re.sub(r'^http(s)?://(www\.)?', '', site) - site = re.sub('/.*(/)?', '', site) +if args.domain: + domain = args.domain.strip() + domain = re.sub(r'^http(s)?://(www\.)?', '', domain) + domain = re.sub('/.*(/)?', '', domain) -if verbose and args.site: - print ("[DEBUG]: Target recieved ==> {}".format(site)) + fox.set_domain(domain) -query_extra = '' if args.ex_query: - query_extra = args.ex_query.strip() + fox.set_ex_args(args.ex_query.strip()) -if verbose and args.ex_query: - print ("[DEBUG]: Extra query parameters to use ==> {}".format(query_extra)) +if args.ua: + fox.set_useragent(args.ua.strip()) -r_query='' -# if raw query provided -if args.query: - r_query = args.query.strip() +if args.threads: + if args.threads > 0: + fox.set_threads(args.threads) + else: + pp.p_error("Please provide some +ve value for threads.") + quit() -if verbose and args.query: - print ("[DEBUG]: Query provided ==> {}".format(r_query)) +if args.delay: + if args.delay > 0: + fox.set_delay_min(args.delay) + fox.set_delay_max(args.delay) + else: + pp.p_error("Please provide some +ve value for delay.") + quit() -inclusive = args.inc +if args.delay_min: + if args.delay_min > 0: + fox.set_delay_min(args.delay_min) + fox.set_delay_max(args.delay_min + 3) + else: + pp.p_error("Please provide some +ve value for delay_min.") + quit() + +if args.delay_max: + if args.delay_max > 0: + fox.set_delay_max(args.delay_max) + if args.delay_max - 3 > 0: + fox.set_delay_min(args.delay_max - 3) + else: + fox.set_delay_min(0) + else: + pp.p_error("Please provide some +ve value for delay_max.") + quit() + +if args.delay_min and args.delay_max: + fox.set_delay_min(args.delay_min) + fox.set_delay_max(args.delay_max) -if inclusive and not args.query: - print ("[ERROR]: Query not found, but inclusive switch is on") +if args.page_size: + if args.page_size <= 0: + pp.p_error("Please provide some +ve value for `dork results`.") + quit() + fox.set_page_size(gsrch.correct_page_size(args.page_size)) + +if args.no_of_pages: + if args.no_of_pages <= 0: + pp.p_error("Please provide some +ve value for `pages to request`.") + quit() + fox.set_no_of_pages(int(args.no_of_pages)) + +if args.max_results: + if args.max_results <= 0: + pp.p_error("Please provide some +ve value for `max results`.") + quit() + fox.set_max_results(args.max_results) + +if ((args.out_fmt_json or args.out_fmt_xml or args.out_report) and + not args.out_dir): + pp.p_error("Output format is defined without specifying output directory.") quit() -if verbose and inclusive: - print ("[DEBUG]: Including dorks results along with query results") +if args.out_silent and not args.out_dir: + pp.p_warn("Can't silent links output, as no output directory defined " + + "to save them.") -category = '' -if args.category: - category = args.category.strip() +if args.out_silent: + fox.set_output_silent(True) -if verbose and category: - print ("[DEBUG]: Category recieved ==> {}".format(category)) +if args.out_dir: + fox.set_outdir(args.out_dir.strip()) -severity = 5 -if args.severity: - severity = args.severity - -if verbose and args.severity: - print ("[DEBUG]: Using severity ==> {}".format(severity)) - -severity_flag = 0 -# 0 for >= severity -# 1 for = severity -# 2 for < severity -if args.s_only: - severity_flag = 1 -if args.s_upper: - severity_flag = 2 - -if args.s_all: - severity = 0 - severity_flag = 0 - -if verbose and args.s_all: - if args.severity: - print ("[DEBUG]: Severity is overridden by `--all` switch") - print ("[DEBUG]: Using severity ==> {}".format(severity)) - -if args.s_qual: - severity = 8 - severity_flag = 0 - -if verbose and args.s_qual: - if args.severity or args.s_all: - print ("[DEBUG]: Severity is overridden by `--quality` switch") - print ("[DEBUG]: Using severity ==> {}".format(severity)) - -if verbose: - print ("[DEBUG]: Severity flag ==> {}".format(severity_flag)) - -page_size = 30 -if args.page_size: - page_size = args.page_size / 10 - - if page_size >= 10: - page_size = 100 - elif page_size >=5: - page_size = 50 - elif page_size > 0 : - page_size = page_size * 10 +if args.out_fmt_json: + fox.set_outmode_json() + +if args.out_fmt_xml: + fox.set_outmode_xml() + +if args.out_report: + fox.set_outmode_report() + +if args.no_ssl_check: + fox.set_ssl_check(False) + +if args.time_out: + if args.time_out <= 0: + pp.p_error("Please provide some +ve value for `request timeout`.") + quit() + fox.set_request_timeout(args.time_out) + +if args.proxy_conn: + if args.proxy_conn > 0: + fox.set_connection_per_proxy(args.proxy_conn) else: - page_size = 30 - -page_size = int(page_size) - -if verbose: - print ("[DEBUG]: Page size ==> {}".format(page_size)) - -dork_size = 150 -if args.dork_size and args.dork_size >= page_size: - dork_size = args.dork_size - -if verbose: - print ("[DEBUG]: Max results per dork ==> {}".format(dork_size)) - -max_results = dork_size * 100 -if args.max_results and args.max_results >= 0: - max_results = args.max_results - -if verbose: - print ("[DEBUG]: Total results limit to ==> {}".format(max_results)) - -delay = 2 -var_delay = 5 -# total delay will be calculated as delay + (var_delay * random_no(0, 1)) - -if args.delay and args.delay >= 0: - delay = args.delay - var_delay = 0 -else: - if args.min and args.max: - if args.min > 0 and args.max >= args.min: - delay = args.min - var_delay = args.max - args.min - elif args.min > 0: - delay = args.min - elif args.min: - if args.min > 0: - delay = args.min - elif args.max: - if args.max >= var_delay : - delay = args.max - var_delay - elif args.max >= 0: - delay = 0 - var_delay = args.max - -if verbose: - print ("[DEBUG]: Delay between each request ==> [{}, {}] sec".format(delay, delay + var_delay)) - -parallel_req = 5 -if args.parallel and args.parallel > 0: - parallel_req = args.parallel - -if verbose: - print ("[DEBUG]: Parallel requests set to ==> {}".format(parallel_req)) - - -useragents = [] -if args.UA: - useragents = args.UA.strip().split(',') -else: -# with open(get_value('useragents'), 'r') as uas: - # useragents = [ua.strip('\n') for ua in uas] - useragents = getUserAgents() - -if verbose: - print ("[DEBUG]: Using User-Agents ==> {}".format(useragents)) - -out_dir = '' - -if args.output: - out_dir = getNewDir(args.output) -else: - print ("[ERROR]: Output directory is not specified") - quit() - -buildReport = True -if args.json and not args.report: - buildReport = False + pp.p_error("Please provide some +ve value for connection per proxy.") -if verbose: - print ("[DEBUG]: Using output directory ==> {}".format(out_dir)) - if not buildReport: - print ("[DEBUG]: Output will be saved in JSON format") - else: - print ("[DEBUG]: Reporting is enabled, along with JSON format") +if args.proxy_open and (args.proxy or args.proxy_file): + pp.p_error("Please use only one option from `open proxies` or " + + "(proxy and proxy_file).") + quit() + +if args.proxy and args.proxy_file: + pp.p_error("Please use only one option from proxy or proxy_file.") + quit() -#---------------------------------------------------------------------------------- +if args.proxy: + fox.add_proxy(args.proxy.strip()) -print("[*] Building db.") -dbBuilder() -print("[*] Finished building db.") +if args.proxy_file: + for i in futil.get_file_aslist(args.proxy_file): + fox.add_proxy(i) -print("[*] Building JSON.") -jsonBuilder(getDir(out_dir, 'dorks')) -print("[*] Finished building JSON.") +if args.proxy_count: + if args.proxy_count <= 0: + pp.p_error("Please provide some +ve value for `open proxy count`.") + quit() -if buildReport: - print("[*] Building Report.") - reportBuilder(out_dir, 'dorks') - print ("[*] Report saved at: {}".format(getFileName(out_dir, 'report.html'))) + fox.set_open_proxy_count(args.proxy_count) + + if fox.is_verbose() and not args.proxy_open: + pp.p_info('Ignoring `open proxy count` as provided without ' + + 'enabling `open proxies` switch.') + +if args.proxy_open: + fox.collect_open_proxies() -print ("[*] Results saved at {}".format(out_dir)) +fox.execute() diff --git a/lib/config.py b/lib/config.py new file mode 100644 index 0000000..7b4f7de --- /dev/null +++ b/lib/config.py @@ -0,0 +1,44 @@ +__all__ = [] + +import configparser + + +class ConfigManager: + config = None + + def getConfig(): + if not ConfigManager.config: + ConfigManager.config = ConfigManager.Configuration() + return ConfigManager.config + + def load(cfile): + conf = configparser.ConfigParser() + conf.read(cfile) + + for s in conf.sections(): + for k in conf[s]: + ConfigManager.getConfig().set(k, conf[s].get(k)) + + return ConfigManager.getConfig() + + class Configuration(): + + def __init__(self): + self.properties = {} + + def get(self, key): + if key in self.properties.keys(): + return self.properties[key] + else: + raise self.NoSuchKeyException("No such key `{}` found" + .format(key)) + + def set(self, key, value): + self.properties[key] = value + + def getKeys(self): + return self.properties.keys() + + class NoSuchKeyException(Exception): + def __init__(self, message): + super().__init__(message) diff --git a/lib/google.py b/lib/google.py new file mode 100644 index 0000000..6e424bb --- /dev/null +++ b/lib/google.py @@ -0,0 +1,116 @@ +__all__ = [] + +import urllib.parse as urlparse +import requests +import threading +import re + + +class GoogleSearch: + _session_var = threading.local() + + def _init_session(): + if not hasattr(GoogleSearch._session_var, "session"): + GoogleSearch._session_var.session = requests.Session() + + def session_cleanup(): + if hasattr(GoogleSearch._session_var, "session"): + delattr(GoogleSearch._session_var, "session") + + def _get_session(): + GoogleSearch._init_session() + return GoogleSearch._session_var.session + + def __qencode__(q): + return urlparse.quote_plus(q) + + def correct_page_size(page_size): + default = 30 + if not page_size: + return default + + try: + page_size = int(page_size) + except: + return default + + page_size /= 10 + # consider lower bound + if page_size >= 10: + page_size = 100 + elif page_size >= 5: + page_size = 50 + elif page_size > 0: + page_size = page_size * 10 + else: + page_size = default + + return int(page_size) + + def prepare_URL(query, site='', params='', page_num=1, page_size=30): + if not query or query == '': + raise Exception("Query cannot be empty") + + if not site: + site = '' + if not params: + params = '' + + u = 'https://www.google.com/search?gbv=1&q=' + u += GoogleSearch.__qencode__(query) + if params != '': + u += '+' + GoogleSearch.__qencode__(params) + if site != '': + u += '+' + GoogleSearch.__qencode__(site) + u += '&btnG=Google+Search' + + page_size = GoogleSearch.correct_page_size(page_size) + + fmt = '{}&start={}&num={}' + page_num = int(page_num) + + if page_num <= 1: + return fmt.format(u, '', page_size) + else: + return fmt.format(u, (page_num - 1) * page_size, page_size) + + def fetch(url, UA='f0x.py (Linux; python/requests)', proxy=None, + time_out=None, sslcheck=True): + hdrs = { + 'Host': 'www.google.com', + 'User-Agent': UA, + 'Accept': 'text/html,application/xhtml+xml,application/xml;' + + ' q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + 'Accept-Encoding': 'gzip, deflate', + 'Referer': 'https://www.google.com/', + 'DNT': '1', + 'Connection': 'keep-alive', + 'Upgrade-Insecure-Requests': '1', + 'Cache-Control': 'max-age=0, no-cache', + 'Pragma': 'no-cache', + 'TE': 'Trailers' + } + + return GoogleSearch._get_session().get(url, headers=hdrs, + proxies=proxy, timeout=time_out, + verify=sslcheck).text + + def has_next(text): + o = re.search('aria-label="Next page"[^>]*>((Next >)|' + + '(]*>>))', text, re.I) + if o: + return True + else: + return False + + def extract_urls(text): + urls = [] + for pat in re.findall('\s+href="/url\?q=([^"&]*)[^"]*"[^>]*>', text, + re.M | re.I): + if not re.search('^http(s)?://(www\.)?[^.]*\.google\.com', pat, + re.I): + up = urlparse.unquote(pat) + if not up.startswith('/search?q='): + urls += [up] + return urls diff --git a/lib/prettyPrint.py b/lib/prettyPrint.py new file mode 100644 index 0000000..2882b52 --- /dev/null +++ b/lib/prettyPrint.py @@ -0,0 +1,161 @@ +__all__ = [] + +from colorama import Fore as f +from colorama import Back as b +from colorama import Style as s + + +class Prettify: + + def __cont__(t, r, c): + return t.replace(r, r + c) + + # *** text *** + def red(text): + return f.RED + Prettify.__cont__(text, f.RESET, f.RED) + f.RESET + + def black(text): + return f.BLACK + Prettify.__cont__(text, f.RESET, f.BLACK) + f.RESET + + def blue(text): + return f.BLUE + Prettify.__cont__(text, f.RESET, f.BLUE) + f.RESET + + def cyan(text): + return f.CYAN + Prettify.__cont__(text, f.RESET, f.CYAN) + f.RESET + + def green(text): + return f.GREEN + Prettify.__cont__(text, f.RESET, f.GREEN) + f.RESET + + def magenta(text): + return f.MAGENTA + Prettify.__cont__(text, f.RESET, f.MAGENTA) + f.RESET + + def white(text): + return f.WHITE + Prettify.__cont__(text, f.RESET, f.WHITE) + f.RESET + + def yellow(text): + return f.YELLOW + Prettify.__cont__(text, f.RESET, f.YELLOW) + f.RESET + + def light_black(text): + return f.LIGHTBLACK_EX + Prettify.__cont__(text, f.RESET, + f.LIGHTBLACK_EX) + f.RESET + + def light_blue(text): + return f.LIGHTBLUE_EX + Prettify.__cont__(text, f.RESET, + f.LIGHTBLUE_EX) + f.RESET + + def light_cyan(text): + return f.LIGHTCYAN_EX + Prettify.__cont__(text, f.RESET, + f.LIGHTCYAN_EX) + f.RESET + + def light_green(text): + return f.LIGHTGREEN_EX + Prettify.__cont__(text, f.RESET, + f.LIGHTGREEN_EX) + f.RESET + + def light_magenta(text): + return f.LIGHTMAGENTA_EX + Prettify.__cont__(text, f.RESET, + f.LIGHTMAGENTA_EX) + \ + f.RESET + + def light_red(text): + return f.LIGHTRED_EX + Prettify.__cont__(text, f.RESET, + f.LIGHTRED_EX) + f.RESET + + def light_white(text): + return f.LIGHTWHITE_EX + Prettify.__cont__(text, f.RESET, + f.LIGHTWHITE_EX) + f.RESET + + def light_yellow(text): + return f.LIGHTYELLOW_EX + Prettify.__cont__(text, f.RESET, + f.LIGHTYELLOW_EX) + f.RESET + + # *** background *** + def on_red(text): + return b.RED + Prettify.__cont__(text, b.RESET, b.RED) + b.RESET + + def on_black(text): + return b.BLACK + Prettify.__cont__(text, b.RESET, b.BLACK) + b.RESET + + def on_blue(text): + return b.BLUE + Prettify.__cont__(text, b.RESET, b.BLUE) + b.RESET + + def on_cyan(text): + return b.CYAN + Prettify.__cont__(text, b.RESET, b.CYAN) + b.RESET + + def on_green(text): + return b.GREEN + Prettify.__cont__(text, b.RESET, b.GREEN) + b.RESET + + def on_magenta(text): + return b.MAGENTA + Prettify.__cont__(text, b.RESET, b.MAGENTA) + b.RESET + + def on_white(text): + return b.WHITE + Prettify.__cont__(text, b.RESET, b.WHITE) + b.RESET + + def on_yellow(text): + return b.YELLOW + Prettify.__cont__(text, b.RESET, b.YELLOW) + b.RESET + + def on_light_black(text): + return b.LIGHTBLACK_EX + Prettify.__cont__(text, b.RESET, + b.LIGHTBLACK_EX) + b.RESET + + def on_light_blue(text): + return b.LIGHTBLUE_EX + Prettify.__cont__(text, b.RESET, + b.LIGHTBLUE_EX) + b.RESET + + def on_light_cyan(text): + return b.LIGHTCYAN_EX + Prettify.__cont__(text, b.RESET, + b.LIGHTCYAN_EX) + b.RESET + + def on_light_green(text): + return b.LIGHTGREEN_EX + Prettify.__cont__(text, b.RESET, + b.LIGHTGREEN_EX) + b.RESET + + def on_light_magenta(text): + return b.LIGHTMAGENTA_EX + Prettify.__cont__(text, b.RESET, + b.LIGHTMAGENTA_EX) + \ + b.RESET + + def on_light_red(text): + return b.LIGHTRED_EX + Prettify.__cont__(text, b.RESET, + b.LIGHTRED_EX) + b.RESET + + def on_light_white(text): + return b.LIGHTWHITE_EX + Prettify.__cont__(text, b.RESET, + b.LIGHTWHITE_EX) + b.RESET + + def on_light_yellow(text): + return b.LIGHTYELLOW_EX + Prettify.__cont__(text, b.RESET, + b.LIGHTYELLOW_EX) + b.RESET + + # *** sytle *** + + def as_bold(text): + return s.BRIGHT + Prettify.__cont__(text, s.RESET_ALL, + s.BRIGHT) + s.RESET_ALL + + def as_dim(text): + return s.DIM + Prettify.__cont__(text, s.RESET_ALL, + s.BRIGHT) + s.RESET_ALL + + def as_normal(text): + return s.NORMAL + Prettify.__cont__(text, s.RESET_ALL, + s.BRIGHT) + s.RESET_ALL + + # *** logger *** + def p_error(text): + print("[{}]: {}".format(Prettify.as_bold(Prettify.red('ERROR')), text)) + + def p_info(text): + print("[{}]: {}".format(Prettify.cyan('INFO'), text)) + + def p_warn(text): + print("[{}]: {}".format(Prettify.red('WARN'), text)) + + def p_debug(text): + print("[{}]: {}".format(Prettify.as_bold(Prettify.yellow('DEBUG')), + text)) + + def p_log(text, header='*'): + print("[{}]: {}".format(Prettify.as_bold(Prettify.blue(header)), text)) + + def p_clog(text, header): + print("[{}]: {}".format(header, text)) diff --git a/lib/useragents.py b/lib/useragents.py new file mode 100644 index 0000000..78ae637 --- /dev/null +++ b/lib/useragents.py @@ -0,0 +1,151 @@ +__all__ = [] + +import random + + +class UA: + __ualist__ = [ + 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.' + + '84 Mobile Safari/537.36', + 'Mozilla/5.0 (Linux; Android 7.0; SM-G892A Build/NRD90M; wv) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 ' + + 'Chrome/60.0.3112.107 Mobile Safari/537.36', + 'Mozilla/5.0 (Linux; Android 7.0; SM-G930VC Build/NRD90M; wv) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 ' + + 'Chrome/58.0.3029.83 Mobile Safari/537.36', + 'Mozilla/5.0 (Linux; Android 6.0.1; SM-G935S Build/MMB29K; wv) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 ' + + 'Chrome/55.0.2883.91 Mobile Safari/537.36', + 'Mozilla/5.0 (Linux; Android 6.0.1; SM-G920V Build/MMB29K) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.' + + '98 Mobile Safari/537.36', + 'Mozilla/5.0 (Linux; Android 5.1.1; SM-G928X Build/LMY47X) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.' + + '83 Mobile Safari/537.36', + 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 6P Build/MMB29P) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.' + + '83 Mobile Safari/537.36', + 'Mozilla/5.0 (Linux; Android 7.1.1; G8231 Build/41.2.A.0.219; wv)' + + ' AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 ' + + 'Chrome/59.0.3071.125 Mobile Safari/537.36', + 'Mozilla/5.0 (Linux; Android 6.0.1; E6653 Build/32.2.A.0.253) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.' + + '98 Mobile Safari/537.36', + 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; ' + + 'Trident/5.0)', + 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 ' + + 'Firefox/15.0.1', + 'Mozilla/5.0 (Linux; Android 6.0; HTC One M9 Build/MRA58K) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.' + + '98 Mobile Safari/537.3', + 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) ' + + 'AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 ' + + 'Mobile/15E148 Safari/604.1', + 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; FSL 7.0.7.' + + '01001)', + 'Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.02', + 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) ' + + 'AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/' + + '69.0.3497.105 Mobile/15E148 Safari/605.1', + 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) ' + + 'AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/13.' + + '2b11866 Mobile/16A366 Safari/605.1.15', + 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; ' + + '.NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET ' + + 'CLR 3.5.30729)', + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) ' + + 'AppleWebKit/604.1.38 (KHTML, like Gecko) Version/' + + '11.0 Mobile/15A372 Safari/604.1', + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) ' + + 'AppleWebKit/604.1.34 (KHTML, like Gecko) Version/' + + '11.0 Mobile/15A5341f Safari/604.1', + 'Mozilla/5.0 (Windows Phone 10.0; Android 6.0.1; Microsoft; ' + + 'RM-1152) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/' + + '52.0.2743.116 Mobile Safari/537.36 Edge/15.15254', + 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; ' + + 'RM-1127_16056) AppleWebKit/537.36(KHTML, like Gecko) ' + + 'Chrome/42.0.2311.135 Mobile Safari/537.36 Edge/12.10536', + 'Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like ' + + 'Gecko) Chrome/13.0.782.112 Safari/535.1', + 'Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; ' + + 'Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) ' + + 'Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.1058', + 'Mozilla/5.0 (Linux; Android 7.0; SM-T827R4 Build/NRD90M) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.' + + '3112.116 Safari/537.36', + 'Mozilla/5.0 (Linux; Android 5.0.2; SAMSUNG SM-T550 Build/LRX22G)' + + ' AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser' + + '/3.3 Chrome/38.0.2125.102 Safari/537.36', + 'Mozilla/5.0 (Linux; Android 4.4.3; KFTHWI Build/KTU84M) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Silk/47.1.79 like' + + ' Chrome/47.0.2526.80 Safari/537.36', + 'Mozilla/5.0 (Linux; Android 5.0.2; LG-V410/V41020c Build/LRX22G)' + + ' AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 ' + + 'Chrome/34.0.1847.118 Safari/537.36', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' + + '(KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 ' + + 'Edge/12.246', + 'Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 ' + + '(KHTML, like Gecko) Chrome/51.0.2704.64 Safari/537.36', + 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET ' + + 'CLR 2.0.50727)', + 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 ' + + 'Firefox/12.0', + 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) ' + + 'AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 ' + + 'Mobile/15A5370a Safari/604.1', + 'Mozilla/5.0 (iPhone9,3; U; CPU iPhone OS 10_0_1 like Mac OS X) ' + + 'AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 ' + + 'Mobile/14A403 Safari/602.1', + 'Mozilla/5.0 (Windows NT 5.1; rv:13.0) Gecko/20100101 Firefox/' + + '13.0.1', + 'Mozilla/5.0 (iPhone9,4; U; CPU iPhone OS 10_0_1 like Mac OS X) ' + + 'AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 ' + + 'Mobile/14A403 Safari/602.1', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/' + + '601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9', + 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, ' + + 'like Gecko) Chrome/47.0.2526.111 Safari/537.36', + 'Mozilla/5.0 (Apple-iPhone7C2/1202.466; U; CPU like Mac OS X; en)' + + ' AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 ' + + 'Mobile/1A543 Safari/419.3', + 'Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 ' + + '(KHTML, like Gecko) Chrome/51.0.2704.64 Safari/537.36', + 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, ' + + 'like Gecko) Chrome/47.0.2526.111 Safari/537.36', + 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 ' + + 'Firefox/15.0.1', + 'Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/' + + 'bingbot.htm)', + 'Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/' + + 'help/us/ysearch/slurp)', + 'Mozilla/5.0 (Linux; Android 6.0.1; SGP771 Build/32.2.A.0.253; ' + + 'wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/' + + '52.0.2743.98 Safari/537.36', + 'Mozilla/4.0 (compatible; MSIE 6.0; MSIE 5.5; Windows NT 5.0) ' + + 'Opera 7.02 Bork-edition [en]', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/' + + '601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' + + '(KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 ' + + 'Edge/12.246', + 'Mozilla/5.0 (Linux; Android 7.0; Pixel C Build/NRD90M; wv) ' + + 'AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 ' + + 'Chrome/52.0.2743.98 Safari/537.36', + 'Mozilla/5.0 (Linux; Android 6.0.1; SHIELD Tablet K1 Build/MRA58K' + + '; wv) AppleWebKit/537.36 (KHTML, like Gecko) ' + + 'Version/4.0 Chrome/55.0.2883.91 Safari/537.36', + 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET ' + + 'CLR 1.0.3705)', + 'Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/' + + 'search/spider.html)', + 'Opera/9.80 (Windows NT 5.1; U; en) Presto/2.10.289 Version/12.01' + ] + + def get_ua_list(): + return UA.__ualist__ + + def get_random_ua(): + return random.choice(UA.__ualist__) + \ No newline at end of file diff --git a/lib/utils.py b/lib/utils.py new file mode 100644 index 0000000..84ea661 --- /dev/null +++ b/lib/utils.py @@ -0,0 +1,136 @@ +__all__ = [] + +import os +import time +import errno +import shutil +import random + + +class DirUtil: + + def join_names(parent, child): + o = parent + if o.endswith('/'): + o += child + else: + o += '/' + child + return o + + def create_dir(parent, dname=''): + o = DirUtil.join_names(parent, dname) + if not os.path.exists(o): + try: + os.makedirs(o) + except OSError as err: + if err.errno != errno.EEXIST: + raise + return o + + def get_dir(parent, child=''): + d = DirUtil.join_names(parent, child) + if not os.path.isdir(d): + raise OSError('Directory not exists.') + return d + + def create_random_dir(parent, prefix=''): + return DirUtil.create_dir(parent, prefix + str(int(time.time() * 1000))) + + def create_temp_dir(parent='', prefix=''): + return DirUtil.create_random_dir(DirUtil.create_dir('/tmp/', parent), + prefix) + + def list_dir(parent): + l = [] + for j in os.listdir(parent): + t = DirUtil.join_names(parent, j) + l += [t] + return l + + def get_dir_list(parent, recurse=False): + l = [] + for i in DirUtil.list_dir(parent): + if os.path.isdir(i): + l += [i] + if recurse: + l += DirUtil.get_dir_list(i, recurse) + return l + + def get_files_list(parent, recurse=False): + l = [] + for i in DirUtil.list_dir(parent): + if os.path.isfile(i): + l += [i] + else: + if recurse: + l += DirUtil.get_files_list(i, recurse) + return l + + def merge_dirs(source, dest): + DirUtil.create_dir(dest) + for i in os.listdir(source): + if os.path.isfile(DirUtil.join_names(source, i)): + shutil.move(DirUtil.join_names(source, i), + DirUtil.join_names(dest, i)) + else: + DirUtil.merge_dirs(DirUtil.join_names(source, i), + DirUtil.create_dir(dest, i)) + + def rmdir(dname): + shutil.rmtree(dname) + + +class FileUtil: + + def join_names(parent, fname): + o = parent + if o.endswith('/'): + o += fname + else: + o += '/' + fname + return o + + def get_file(parent, fname): + f = FileUtil.join_names(parent, fname) + if not os.path.isfile(f): + raise OSError('File not exists') + return f + + def create_random_file(parent, prefix=''): + return FileUtil.join_names(parent, prefix + str(int(time.time() * + 1000))) + + def create_temp_file(dname='', prefix=''): + return FileUtil.create_random_file(FileUtil.join_names('/tmp/', dname), + prefix) + + def dump_list(ofile, l, append=True): + if l: + m = 'a' + if not append: + m = 'w' + with open(ofile, m) as f: + for i in l: + f.write("{}\n".format(i)) + + def get_file_aslist(file): + list = [] + + with open(file, 'r') as f: + list = [l.strip() for l in f if l.strip()] + + return list + + +class Random: + + def rand_between(start, end): + if start < 0: + raise Exception('Require positive number') + if end < 0: + raise Exception('Require positive number') + + return start + ((end - start) * random.random()) + + def rand_no(max_no): + return Random.rand_between(0, max_no) diff --git a/user-agents b/user-agents deleted file mode 100644 index 033e5a1..0000000 --- a/user-agents +++ /dev/null @@ -1,53 +0,0 @@ -Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36 -Mozilla/5.0 (Linux; Android 7.0; SM-G892A Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/60.0.3112.107 Mobile Safari/537.36 -Mozilla/5.0 (Linux; Android 7.0; SM-G930VC Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/58.0.3029.83 Mobile Safari/537.36 -Mozilla/5.0 (Linux; Android 6.0.1; SM-G935S Build/MMB29K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Mobile Safari/537.36 -Mozilla/5.0 (Linux; Android 6.0.1; SM-G920V Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36 -Mozilla/5.0 (Linux; Android 5.1.1; SM-G928X Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Mobile Safari/537.36 -Mozilla/5.0 (Linux; Android 6.0.1; Nexus 6P Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.83 Mobile Safari/537.36 -Mozilla/5.0 (Linux; Android 7.1.1; G8231 Build/41.2.A.0.219; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/59.0.3071.125 Mobile Safari/537.36 -Mozilla/5.0 (Linux; Android 6.0.1; E6653 Build/32.2.A.0.253) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36 -Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0) -Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1 -Mozilla/5.0 (Linux; Android 6.0; HTC One M9 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.3 -Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1 -Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; FSL 7.0.7.01001) -Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.02 -Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/69.0.3497.105 Mobile/15E148 Safari/605.1 -Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/13.2b11866 Mobile/16A366 Safari/605.1.15 -Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729) -Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1 -Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1 -Mozilla/5.0 (Windows Phone 10.0; Android 6.0.1; Microsoft; RM-1152) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Mobile Safari/537.36 Edge/15.15254 -Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; RM-1127_16056) AppleWebKit/537.36(KHTML, like Gecko) Chrome/42.0.2311.135 Mobile Safari/537.36 Edge/12.10536 -Mozilla/5.0 (Windows NT 6.0) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.112 Safari/535.1 -Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.1058 -Mozilla/5.0 (Linux; Android 7.0; SM-T827R4 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.116 Safari/537.36 -Mozilla/5.0 (Linux; Android 5.0.2; SAMSUNG SM-T550 Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/3.3 Chrome/38.0.2125.102 Safari/537.36 -Mozilla/5.0 (Linux; Android 4.4.3; KFTHWI Build/KTU84M) AppleWebKit/537.36 (KHTML, like Gecko) Silk/47.1.79 like Chrome/47.0.2526.80 Safari/537.36 -Mozilla/5.0 (Linux; Android 5.0.2; LG-V410/V41020c Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/34.0.1847.118 Safari/537.36 -Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246 -Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.64 Safari/537.36 -Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727) -Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0 -Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A5370a Safari/604.1 -Mozilla/5.0 (iPhone9,3; U; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1 -Mozilla/5.0 (Windows NT 5.1; rv:13.0) Gecko/20100101 Firefox/13.0.1 -Mozilla/5.0 (iPhone9,4; U; CPU iPhone OS 10_0_1 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/14A403 Safari/602.1 -Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9 -Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 -Mozilla/5.0 (Apple-iPhone7C2/1202.466; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543 Safari/419.3 -Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.64 Safari/537.36 -Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36 -Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1 -Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) -Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp) -Mozilla/5.0 (Linux; Android 6.0.1; SGP771 Build/32.2.A.0.253; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/52.0.2743.98 Safari/537.36 -Mozilla/4.0 (compatible; MSIE 6.0; MSIE 5.5; Windows NT 5.0) Opera 7.02 Bork-edition [en] -Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/601.3.9 (KHTML, like Gecko) Version/9.0.2 Safari/601.3.9 -Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246 -Mozilla/5.0 (Linux; Android 7.0; Pixel C Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/52.0.2743.98 Safari/537.36 -Mozilla/5.0 (Linux; Android 6.0.1; SHIELD Tablet K1 Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/55.0.2883.91 Safari/537.36 -Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705) -Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html) -Opera/9.80 (Windows NT 5.1; U; en) Presto/2.10.289 Version/12.01