Files
Arjun/arjun/__main__.py

235 lines
10 KiB
Python
Raw Normal View History

2018-11-09 20:32:08 +05:30
#!/usr/bin/env python3
2020-12-06 15:06:15 +05:30
# -*- coding: utf-8 -*-
2018-11-09 20:32:08 +05:30
2021-02-07 19:43:30 +05:30
from arjun.core.colors import green, end, info, bad, good, run, res
2018-03-01 19:16:28 +05:30
import argparse
2022-09-11 14:36:51 +05:30
import json
2020-12-06 15:06:15 +05:30
from urllib.parse import urlparse
2021-02-07 19:43:30 +05:30
import arjun.core.config as mem
from arjun.core.exporter import exporter
2022-09-11 02:57:13 +05:30
from arjun.core.anomaly import define, compare
2021-03-11 05:37:23 +05:30
from arjun.core.utils import fetch_params, stable_request, random_str, slicer, confirm, populate, reader, nullify, prepare_requests, compatible_path
2020-12-06 15:06:15 +05:30
2021-02-07 19:43:30 +05:30
from arjun.plugins.heuristic import heuristic
2018-03-01 19:16:28 +05:30
2021-06-15 23:01:34 +05:30
arjun_dir = compatible_path(mem.__file__.replace(compatible_path('/core/config.py'), ''))
2021-02-08 08:18:43 +05:30
2019-10-23 12:59:47 +05:30
parser = argparse.ArgumentParser() # defines the parser
# Arguments that can be supplied
2021-06-10 13:02:44 +03:00
parser.add_argument('-u', help='Target URL', dest='url')
parser.add_argument('-o', '-oJ', help='Path for json output file.', dest='json_file')
parser.add_argument('-oT', help='Path for text output file.', dest='text_file')
2024-01-08 11:55:50 +07:00
parser.add_argument('-oB', help='Output to Burp Suite Proxy. Default is 127.0.0.1:8080.', dest='burp_proxy', nargs='?', const='127.0.0.1:8080')
2021-06-10 13:02:44 +03:00
parser.add_argument('-d', help='Delay between requests in seconds. (default: 0)', dest='delay', type=float, default=0)
2022-09-11 02:57:13 +05:30
parser.add_argument('-t', help='Number of concurrent threads. (default: 5)', dest='threads', type=int, default=5)
2022-04-04 14:47:27 +05:30
parser.add_argument('-w', help='Wordlist file path. (default: {arjundir}/db/large.txt)', dest='wordlist', default=arjun_dir+'/db/large.txt')
2022-09-11 14:36:51 +05:30
parser.add_argument('-m', help='Request method to use: GET/POST/XML/JSON/HEADERS. (default: GET)', dest='method', default='GET')
2021-06-10 13:02:44 +03:00
parser.add_argument('-i', help='Import target URLs from file.', dest='import_file', nargs='?', const=True)
parser.add_argument('-T', help='HTTP request timeout in seconds. (default: 15)', dest='timeout', type=float, default=15)
2023-11-16 18:00:17 +05:30
parser.add_argument('-c', help='Chunk size. The number of parameters to be sent at once', type=int, dest='chunks', default=250)
parser.add_argument('-q', help='Quiet mode. No output.', dest='quiet', action='store_true')
2024-02-29 15:38:42 +05:30
parser.add_argument('--rate-limit', help='Max number of requests to be sent out per second (default: 9999)', dest='rate_limit', type=int, default=9999)
2021-06-10 13:02:44 +03:00
parser.add_argument('--headers', help='Add headers. Separate multiple headers with a new line.', dest='headers', nargs='?', const=True)
parser.add_argument('--passive', help='Collect parameter names from passive sources like wayback, commoncrawl and otx.', dest='passive', nargs='?', const='-')
parser.add_argument('--stable', help='Prefer stability over speed.', dest='stable', action='store_true')
parser.add_argument('--include', help='Include this data in every request.', dest='include', default={})
2022-04-09 21:35:06 +05:30
parser.add_argument('--disable-redirects', help='disable redirects', dest='disable_redirects', action='store_true')
2019-10-23 12:59:47 +05:30
args = parser.parse_args() # arguments to be parsed
2018-03-01 19:16:28 +05:30
2021-02-07 19:43:30 +05:30
if args.quiet:
print = nullify
print('''%s _
/_| _ '
( |/ /(//) v%s
_/ %s
''' % (green, __import__('arjun').__version__, end))
try:
from concurrent.futures import ThreadPoolExecutor, as_completed
except ImportError:
print('%s Please use Python > 3.2 to run Arjun.' % bad)
quit()
2020-12-06 15:06:15 +05:30
mem.var = vars(args)
2020-12-06 15:26:32 +05:30
mem.var['method'] = mem.var['method'].upper()
2023-11-16 18:00:17 +05:30
if mem.var['method'] != 'GET':
mem.var['chunks'] = 500
2020-12-06 15:06:15 +05:30
if mem.var['stable'] or mem.var['delay']:
mem.var['threads'] = 1
2022-04-04 14:47:27 +05:30
if mem.var['wordlist'] in ('large', 'medium', 'small'):
mem.var['wordlist'] = f'{arjun_dir}/db/{mem.var["wordlist"]}.txt'
2018-03-01 19:16:28 +05:30
2018-11-09 20:32:08 +05:30
try:
2021-06-15 23:01:34 +05:30
wordlist_file = arjun_dir + '/db/small.txt' if args.wordlist == 'small' else args.wordlist
2021-03-11 05:37:23 +05:30
wordlist_file = compatible_path(wordlist_file)
2021-02-07 19:43:30 +05:30
wordlist = set(reader(wordlist_file, mode='lines'))
2020-12-06 15:06:15 +05:30
if mem.var['passive']:
host = mem.var['passive']
if host == '-':
host = urlparse(args.url).netloc
print('%s Collecting parameter names from passive sources for %s, it may take a while' % (run, host))
passive_params = fetch_params(host)
wordlist.update(passive_params)
print('%s Collected %s parameters, added to the wordlist' % (info, len(passive_params)))
wordlist = list(wordlist)
2018-11-09 20:32:08 +05:30
except FileNotFoundError:
2020-12-06 15:06:15 +05:30
exit('%s The specified file for parameters doesn\'t exist' % bad)
2020-12-08 20:55:17 +05:30
if len(wordlist) < mem.var['chunks']:
2021-02-07 19:43:30 +05:30
mem.var['chunks'] = int(len(wordlist)/2)
if not args.url and not args.import_file:
exit('%s No target(s) specified' % bad)
2020-12-06 15:06:15 +05:30
2024-03-01 11:38:55 +05:30
from arjun.core.requester import requester
from arjun.core.bruter import bruter
2020-12-06 15:06:15 +05:30
def narrower(request, factors, param_groups):
2021-02-07 19:43:30 +05:30
"""
takes a list of parameters and narrows it down to parameters that cause anomalies
returns list
"""
2021-07-13 14:18:21 +02:00
anomalous_params = []
2020-12-06 15:06:15 +05:30
threadpool = ThreadPoolExecutor(max_workers=mem.var['threads'])
futures = (threadpool.submit(bruter, request, factors, params) for params in param_groups)
for i, result in enumerate(as_completed(futures)):
2019-03-02 07:31:47 +05:30
if result.result():
2021-07-13 14:18:21 +02:00
anomalous_params.extend(slicer(result.result()))
2022-04-04 14:47:27 +05:30
if mem.var['kill']:
return anomalous_params
print('%s Processing chunks: %i/%-6i' % (info, i + 1, len(param_groups)), end='\r')
2021-07-13 14:18:21 +02:00
return anomalous_params
2020-12-06 15:06:15 +05:30
2021-02-07 19:43:30 +05:30
2022-09-11 02:57:13 +05:30
def initialize(request, wordlist, single_url=False):
2021-02-07 19:43:30 +05:30
"""
handles parameter finding process for a single request object
returns 'skipped' (on error), list on success
"""
2020-12-06 15:06:15 +05:30
url = request['url']
2020-12-06 23:57:06 +05:30
if not url.startswith('http'):
print('%s %s is not a valid URL' % (bad, url))
return 'skipped'
2020-12-06 15:06:15 +05:30
print('%s Probing the target for stability' % run)
2022-04-04 14:47:27 +05:30
request['url'] = stable_request(url, request['headers'])
if not request['url']:
2020-12-06 15:06:15 +05:30
return 'skipped'
2019-10-23 12:59:47 +05:30
else:
2023-11-16 18:00:17 +05:30
fuzz = "z" + random_str(6)
response_1 = requester(request, {fuzz[:-1]: fuzz[::-1][:-1]})
2022-09-11 02:57:13 +05:30
if single_url:
print('%s Analysing HTTP response for anomalies' % run)
2023-11-16 18:00:17 +05:30
response_2 = requester(request, {fuzz[:-1]: fuzz[::-1][:-1]})
2020-12-06 15:06:15 +05:30
if type(response_1) == str or type(response_2) == str:
return 'skipped'
2023-11-16 18:00:17 +05:30
# params from response must be extracted before factors but displayed later
found, words_exist = heuristic(response_1, wordlist)
2020-12-06 15:06:15 +05:30
factors = define(response_1, response_2, fuzz, fuzz[::-1], wordlist)
2023-11-16 18:00:17 +05:30
zzuf = "z" + random_str(6)
response_3 = requester(request, {zzuf[:-1]: zzuf[::-1][:-1]})
while factors:
reason = compare(response_3, factors, {zzuf[:-1]: zzuf[::-1][:-1]})[2]
if not reason:
break
factors[reason] = []
2022-09-11 02:57:13 +05:30
if single_url:
print('%s Analysing HTTP response for potential parameter names' % run)
2020-12-06 15:06:15 +05:30
if found:
num = len(found)
2022-09-11 14:36:51 +05:30
if words_exist:
print('%s Heuristic scanner found %i parameters' % (good, num))
else:
s = 's' if num > 1 else ''
print('%s Heuristic scanner found %i parameter%s: %s' % (good, num, s, ', '.join(found)))
2022-09-11 02:57:13 +05:30
if single_url:
print('%s Logicforcing the URL endpoint' % run)
2020-12-06 15:06:15 +05:30
populated = populate(wordlist)
2022-09-11 14:36:51 +05:30
with open(f'{arjun_dir}/db/special.json', 'r') as f:
populated.update(json.load(f))
2020-12-08 20:55:17 +05:30
param_groups = slicer(populated, int(len(wordlist)/mem.var['chunks']))
2022-09-11 02:57:13 +05:30
prev_chunk_count = len(param_groups)
2020-12-06 15:06:15 +05:30
last_params = []
2019-10-23 12:59:47 +05:30
while True:
2020-12-06 15:06:15 +05:30
param_groups = narrower(request, factors, param_groups)
2022-09-11 02:57:13 +05:30
if len(param_groups) > prev_chunk_count:
2023-11-16 18:00:17 +05:30
response_3 = requester(request, {zzuf[:-1]: zzuf[::-1][:-1]})
if compare(response_3, factors, {zzuf[:-1]: zzuf[::-1][:-1]})[0] != '':
print('%s Target is misbehaving. Try the --stable switch.' % bad)
2022-09-11 02:57:13 +05:30
return []
2020-12-06 15:06:15 +05:30
if mem.var['kill']:
return 'skipped'
param_groups = confirm(param_groups, last_params)
2022-09-11 02:57:13 +05:30
prev_chunk_count = len(param_groups)
2020-12-06 15:06:15 +05:30
if not param_groups:
2019-07-02 13:53:54 +05:30
break
2020-12-06 15:06:15 +05:30
confirmed_params = []
for param in last_params:
reason = bruter(request, factors, param, mode='verify')
if reason:
name = list(param.keys())[0]
confirmed_params.append(name)
2022-09-11 14:36:51 +05:30
if single_url:
print('%s parameter detected: %s, based on: %s' % (res, name, reason))
2020-12-06 15:06:15 +05:30
return confirmed_params
2021-02-07 19:43:30 +05:30
def main():
request = prepare_requests(args)
2019-10-23 12:59:47 +05:30
2021-02-07 19:43:30 +05:30
final_result = {}
try:
if type(request) == dict:
# in case of a single target
2020-12-06 15:06:15 +05:30
mem.var['kill'] = False
2021-02-07 19:43:30 +05:30
url = request['url']
2022-09-11 14:36:51 +05:30
these_params = initialize(request, wordlist, single_url=True)
2020-12-06 15:06:15 +05:30
if these_params == 'skipped':
2021-02-07 19:43:30 +05:30
print('%s Skipped %s due to errors' % (bad, request['url']))
2021-02-08 08:18:43 +05:30
elif these_params:
2020-12-06 15:06:15 +05:30
final_result[url] = {}
final_result[url]['params'] = these_params
2021-02-07 19:43:30 +05:30
final_result[url]['method'] = request['method']
final_result[url]['headers'] = request['headers']
2022-09-11 14:36:51 +05:30
print('%s Parameters found: %s' % (good, ', '.join(final_result[url]['params'])))
2022-09-11 02:57:13 +05:30
exporter(final_result)
else:
print('%s No parameters were discovered.' % info)
2021-02-07 19:43:30 +05:30
elif type(request) == list:
# in case of multiple targets
2022-09-11 02:57:13 +05:30
count = 0
2021-02-07 19:43:30 +05:30
for each in request:
2022-09-11 02:57:13 +05:30
count += 1
2021-02-07 19:43:30 +05:30
url = each['url']
mem.var['kill'] = False
2022-09-11 02:57:13 +05:30
print('%s Scanning %d/%d: %s' % (run, count, len(request), url))
2021-02-07 19:43:30 +05:30
these_params = initialize(each, list(wordlist))
if these_params == 'skipped':
print('%s Skipped %s due to errors' % (bad, url))
elif these_params:
final_result[url] = {}
final_result[url]['params'] = these_params
final_result[url]['method'] = each['method']
final_result[url]['headers'] = each['headers']
2022-09-11 02:57:13 +05:30
exporter(final_result)
print('%s Parameters found: %s\n' % (good, ', '.join(final_result[url]['params'])))
if not mem.var['json_file']:
final_result = {}
continue
else:
print('%s No parameters were discovered.\n' % info)
2021-02-07 19:43:30 +05:30
except KeyboardInterrupt:
exit()
if __name__ == '__main__':
main()