2.1.0 build
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,3 +1,13 @@
|
||||
#### 2.1.0
|
||||
- Added `XML` method
|
||||
- `-q` option for quiet mode
|
||||
- New wordlists backed by research
|
||||
- `-oT` option for txt export
|
||||
- `-oB` option for BurpSuite export
|
||||
- `-oJ` alias for JSON export
|
||||
- Added support for custom injection point in `XML` and `JSON`
|
||||
- pypi package
|
||||
|
||||
#### 2.0-beta
|
||||
- Added an anamoly detection algorithm with 9 factors
|
||||
- Added a HTTP response analyzer for handling errors and retrying requests
|
||||
|
||||
1
arjun/__init__.py
Normal file
1
arjun/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
__version__ = '2.0.1'
|
||||
@@ -1,15 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import print_function
|
||||
from arjun.core.colors import green, end, info, bad, good, run, res
|
||||
|
||||
from core.colors import green, end, info, bad, good, run, res
|
||||
import os
|
||||
import argparse
|
||||
|
||||
from urllib.parse import urlparse
|
||||
import arjun.core.config as mem
|
||||
from arjun.core.bruter import bruter
|
||||
from arjun.core.exporter import exporter
|
||||
from arjun.core.requester import requester
|
||||
from arjun.core.anomaly import define
|
||||
from arjun.core.utils import fetch_params, stable_request, random_str, slicer, confirm, populate, reader, nullify, prepare_requests
|
||||
|
||||
from arjun.plugins.heuristic import heuristic
|
||||
|
||||
parser = argparse.ArgumentParser() # defines the parser
|
||||
# Arguments that can be supplied
|
||||
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')
|
||||
parser.add_argument('-oB', help='port for burp suite proxy', dest='burp_port')
|
||||
parser.add_argument('-d', help='delay between requests', dest='delay', type=float, default=0)
|
||||
parser.add_argument('-t', help='number of threads', dest='threads', type=int, default=2)
|
||||
parser.add_argument('-w', help='wordlist path', dest='wordlist', default=os.getcwd()+'/db/default.txt')
|
||||
parser.add_argument('-m', help='request method: GET/POST/XML/JSON', dest='method', default='GET')
|
||||
parser.add_argument('-i', help='import targets from file', dest='import_file', nargs='?', const=True)
|
||||
parser.add_argument('-T', help='http request timeout', dest='timeout', type=float, default=15)
|
||||
parser.add_argument('-c', help='chunk size/number of parameters to be sent at once', type=int, dest='chunks', default=500)
|
||||
parser.add_argument('-q', help='quiet mode, no output', dest='quiet', action='store_true')
|
||||
parser.add_argument('--headers', help='add headers', dest='headers', nargs='?', const=True)
|
||||
parser.add_argument('--passive', help='collect parameter names from passive sources', dest='passive')
|
||||
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={})
|
||||
args = parser.parse_args() # arguments to be parsed
|
||||
|
||||
if args.quiet:
|
||||
print = nullify
|
||||
|
||||
print('''%s _
|
||||
/_| _ '
|
||||
( |/ /(//) v2.0-beta
|
||||
( |/ /(//) v%s
|
||||
_/ %s
|
||||
''' % (green, end))
|
||||
''' % (green, __import__('arjun').__version__, end))
|
||||
|
||||
try:
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
@@ -17,39 +51,6 @@ except ImportError:
|
||||
print('%s Please use Python > 3.2 to run Arjun.' % bad)
|
||||
quit()
|
||||
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import core.config as mem
|
||||
from core.bruter import bruter
|
||||
from core.prompt import prompt
|
||||
from core.importer import importer
|
||||
from core.requester import requester
|
||||
from core.anamoly import define
|
||||
from core.utils import fetch_params, stable_request, randomString, slicer, confirm, getParams, populate, extractHeaders, reader
|
||||
|
||||
from plugins.heuristic import heuristic
|
||||
|
||||
parser = argparse.ArgumentParser() # defines the parser
|
||||
# Arguments that can be supplied
|
||||
parser.add_argument('-u', help='target url', dest='url')
|
||||
parser.add_argument('-o', help='path for the output file', dest='output_file')
|
||||
parser.add_argument('-d', help='delay between requests', dest='delay', type=float, default=0)
|
||||
parser.add_argument('-t', help='number of threads', dest='threads', type=int, default=2)
|
||||
parser.add_argument('-w', help='wordlist path', dest='wordlist', default=sys.path[0]+'/db/params.txt')
|
||||
parser.add_argument('-m', help='request method: GET/POST/JSON', dest='method', default='GET')
|
||||
parser.add_argument('-i', help='import targets from file', dest='import_file', nargs='?', const=True)
|
||||
parser.add_argument('-T', help='http request timeout', dest='timeout', type=float, default=15)
|
||||
parser.add_argument('-c', help='chunk size/number of parameters to be sent at once', type=int, dest='chunks', default=500)
|
||||
parser.add_argument('--headers', help='add headers', dest='headers', nargs='?', const=True)
|
||||
parser.add_argument('--passive', help='collect parameter names from passive sources', dest='passive')
|
||||
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={})
|
||||
args = parser.parse_args() # arguments to be parsed
|
||||
|
||||
mem.var = vars(args)
|
||||
|
||||
mem.var['method'] = mem.var['method'].upper()
|
||||
@@ -58,7 +59,8 @@ if mem.var['stable'] or mem.var['delay']:
|
||||
mem.var['threads'] = 1
|
||||
|
||||
try:
|
||||
wordlist = set(reader(args.wordlist, mode='lines'))
|
||||
wordlist_file = os.getcwd() + '/db/small.txt' if args.wordlist == 'small' else args.wordlist
|
||||
wordlist = set(reader(wordlist_file, mode='lines'))
|
||||
if mem.var['passive']:
|
||||
host = mem.var['passive']
|
||||
if host == '-':
|
||||
@@ -74,38 +76,15 @@ except FileNotFoundError:
|
||||
if len(wordlist) < mem.var['chunks']:
|
||||
mem.var['chunks'] = int(len(wordlist)/2)
|
||||
|
||||
if not (args.url, args.import_file):
|
||||
exit('%s No targets specified' % bad)
|
||||
|
||||
def prepare_requests(args):
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0',
|
||||
'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',
|
||||
'Connection': 'close',
|
||||
'Upgrade-Insecure-Requests': '1'
|
||||
}
|
||||
if type(headers) == bool:
|
||||
headers = extractHeaders(prompt())
|
||||
elif type(headers) == str:
|
||||
headers = extractHeaders(headers)
|
||||
if mem.var['method'] == 'JSON':
|
||||
headers['Content-type'] = 'application/json'
|
||||
if args.url:
|
||||
params = getParams(args.include)
|
||||
return {
|
||||
'url': args.url,
|
||||
'method': mem.var['method'],
|
||||
'headers': headers,
|
||||
'include': params
|
||||
}
|
||||
elif args.import_file:
|
||||
return importer(args.import_file, mem.var['method'], headers, args.include)
|
||||
return []
|
||||
if not args.url and not args.import_file:
|
||||
exit('%s No target(s) specified' % bad)
|
||||
|
||||
|
||||
def narrower(request, factors, param_groups):
|
||||
"""
|
||||
takes a list of parameters and narrows it down to parameters that cause anomalies
|
||||
returns list
|
||||
"""
|
||||
anamolous_params = []
|
||||
threadpool = ThreadPoolExecutor(max_workers=mem.var['threads'])
|
||||
futures = (threadpool.submit(bruter, request, factors, params) for params in param_groups)
|
||||
@@ -116,7 +95,12 @@ def narrower(request, factors, param_groups):
|
||||
print('%s Processing chunks: %i/%-6i' % (info, i + 1, len(param_groups)), end='\r')
|
||||
return anamolous_params
|
||||
|
||||
|
||||
def initialize(request, wordlist):
|
||||
"""
|
||||
handles parameter finding process for a single request object
|
||||
returns 'skipped' (on error), list on success
|
||||
"""
|
||||
url = request['url']
|
||||
if not url.startswith('http'):
|
||||
print('%s %s is not a valid URL' % (bad, url))
|
||||
@@ -126,11 +110,11 @@ def initialize(request, wordlist):
|
||||
if not stable:
|
||||
return 'skipped'
|
||||
else:
|
||||
fuzz = randomString(6)
|
||||
response_1 = requester(request, {fuzz : fuzz[::-1]})
|
||||
fuzz = random_str(6)
|
||||
response_1 = requester(request, {fuzz: fuzz[::-1]})
|
||||
print('%s Analysing HTTP response for anamolies' % run)
|
||||
fuzz = randomString(6)
|
||||
response_2 = requester(request, {fuzz : fuzz[::-1]})
|
||||
fuzz = random_str(6)
|
||||
response_2 = requester(request, {fuzz: fuzz[::-1]})
|
||||
if type(response_1) == str or type(response_2) == str:
|
||||
return 'skipped'
|
||||
factors = define(response_1, response_2, fuzz, fuzz[::-1], wordlist)
|
||||
@@ -157,25 +141,30 @@ def initialize(request, wordlist):
|
||||
if reason:
|
||||
name = list(param.keys())[0]
|
||||
confirmed_params.append(name)
|
||||
print('%s name: %s, factor: %s' % (res, name, reason))
|
||||
print('%s name: %s, factor: 4%s' % (res, name, reason))
|
||||
return confirmed_params
|
||||
|
||||
request = prepare_requests(args)
|
||||
|
||||
final_result = {}
|
||||
def main():
|
||||
request = prepare_requests(args)
|
||||
|
||||
try:
|
||||
final_result = {}
|
||||
|
||||
try:
|
||||
if type(request) == dict:
|
||||
# in case of a single target
|
||||
mem.var['kill'] = False
|
||||
url = request['url']
|
||||
these_params = initialize(request, wordlist)
|
||||
if these_params == 'skipped':
|
||||
print('%s Skipped %s due to errors' % (bad, request['url']))
|
||||
elif these_params:
|
||||
final_result['url'] = url
|
||||
final_result['params'] = these_params
|
||||
final_result['method'] = request['method']
|
||||
elif these_ppiparams:
|
||||
final_result[url] = {}
|
||||
final_result[url]['params'] = these_params
|
||||
final_result[url]['method'] = request['method']
|
||||
final_result[url]['headers'] = request['headers']
|
||||
elif type(request) == list:
|
||||
# in case of multiple targets
|
||||
for each in request:
|
||||
url = each['url']
|
||||
mem.var['kill'] = False
|
||||
@@ -187,12 +176,13 @@ try:
|
||||
final_result[url] = {}
|
||||
final_result[url]['params'] = these_params
|
||||
final_result[url]['method'] = each['method']
|
||||
final_result[url]['headers'] = each['headers']
|
||||
print('%s Parameters found: %s' % (good, ', '.join(final_result[url])))
|
||||
except KeyboardInterrupt:
|
||||
except KeyboardInterrupt:
|
||||
exit()
|
||||
|
||||
# Finally, export to json
|
||||
if args.output_file and final_result:
|
||||
with open(str(mem.var['output_file']), 'w+', encoding='utf8') as json_output:
|
||||
json.dump(final_result, json_output, sort_keys=True, indent=4)
|
||||
print('%s Output saved to JSON file in %s' % (info, mem.var['output_file']))
|
||||
exporter(final_result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
import re
|
||||
|
||||
from core.utils import lcs, removeTags
|
||||
|
||||
def diff_map(body_1, body_2):
|
||||
sig = []
|
||||
lines_1, lines_2 = body_1.split('\n'), body_2.split('\n')
|
||||
for line_1, line_2 in zip(lines_1, lines_2):
|
||||
if line_1 == line_2:
|
||||
sig.append(line_1)
|
||||
return sig
|
||||
from arjun.core.utils import lcs, diff_map, remove_tags
|
||||
|
||||
|
||||
def define(response_1, response_2, param, value, wordlist):
|
||||
"""
|
||||
defines a rule list for detecting anomalies by comparing two HTTP response
|
||||
returns dict
|
||||
"""
|
||||
factors = {
|
||||
'same_code': False, # if http status code is same, contains that code
|
||||
'same_body': False, # if http body is same, contains that body
|
||||
@@ -23,7 +19,7 @@ def define(response_1, response_2, param, value, wordlist):
|
||||
'param_missing': False, # if param name is missing from the body, contains words that are already there
|
||||
'value_missing': False # contains whether param value is missing from the body
|
||||
}
|
||||
if (response_1 and response_2) != None:
|
||||
if response_1 and response_2:
|
||||
body_1, body_2 = response_1.text, response_2.text
|
||||
if response_1.status_code == response_2.status_code:
|
||||
factors['same_code'] = response_1.status_code
|
||||
@@ -33,8 +29,8 @@ def define(response_1, response_2, param, value, wordlist):
|
||||
factors['same_redirect'] = response_1.url
|
||||
if response_1.text == response_2.text:
|
||||
factors['same_body'] = response_1.text
|
||||
elif removeTags(body_1) == removeTags(body_2):
|
||||
factors['same_plaintext'] = removeTags(body_1)
|
||||
elif remove_tags(body_1) == remove_tags(body_2):
|
||||
factors['same_plaintext'] = remove_tags(body_1)
|
||||
elif body_1 and body_2:
|
||||
if body_1.count('\\n') == 1:
|
||||
factors['common_string'] = lcs(body_1, body_2)
|
||||
@@ -48,6 +44,10 @@ def define(response_1, response_2, param, value, wordlist):
|
||||
|
||||
|
||||
def compare(response, factors, params):
|
||||
"""
|
||||
detects anomalies by comparing a HTTP response against a rule list
|
||||
returns string, list (anamoly, list of parameters that caused it)
|
||||
"""
|
||||
if factors['same_code'] and response.status_code != factors['same_code']:
|
||||
return ('http code', params)
|
||||
if factors['same_headers'] and list(response.headers.keys()) != factors['same_headers']:
|
||||
@@ -56,7 +56,7 @@ def compare(response, factors, params):
|
||||
return ('redirection', params)
|
||||
if factors['same_body'] and response.text != factors['same_body']:
|
||||
return ('body length', params)
|
||||
if factors['same_plaintext'] and removeTags(response.text) != factors['same_plaintext']:
|
||||
if factors['same_plaintext'] and remove_tags(response.text) != factors['same_plaintext']:
|
||||
return ('text length', params)
|
||||
if factors['lines_diff']:
|
||||
for line in factors['lines_diff']:
|
||||
@@ -1,11 +1,15 @@
|
||||
import core.config as mem
|
||||
import arjun.core.config as mem
|
||||
|
||||
from core.anamoly import compare
|
||||
from core.requester import requester
|
||||
from core.error_handler import error_handler
|
||||
from arjun.core.anomaly import compare
|
||||
from arjun.core.requester import requester
|
||||
from arjun.core.error_handler import error_handler
|
||||
|
||||
|
||||
def bruter(request, factors, params, mode='bruteforce'):
|
||||
"""
|
||||
returns anomaly detection result for a chunk of parameters
|
||||
returns list
|
||||
"""
|
||||
if mem.var['kill']:
|
||||
return []
|
||||
response = requester(request, params)
|
||||
|
||||
@@ -1 +1 @@
|
||||
var = {}
|
||||
var = {} # all the cli arguments are added to this variable to be accessed globally
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import time
|
||||
|
||||
import core.config as mem
|
||||
import arjun.core.config as mem
|
||||
|
||||
from core.colors import bad
|
||||
from arjun.core.colors import bad
|
||||
|
||||
def connection_refused():
|
||||
"""
|
||||
checks if a request should be retried if the server refused connection
|
||||
returns str
|
||||
"""
|
||||
if mem.var['stable']:
|
||||
print('%s Hit rate limit, stabilizing the connection' % bad)
|
||||
mem.var['kill'] = False
|
||||
@@ -14,6 +18,13 @@ def connection_refused():
|
||||
return 'kill'
|
||||
|
||||
def error_handler(response, factors):
|
||||
"""
|
||||
decides what to do after performing a HTTP request
|
||||
'ok': continue normally
|
||||
'retry': retry this request
|
||||
'kill': stop processing this target
|
||||
returns str
|
||||
"""
|
||||
if type(response) != str and response.status_code in (400, 503, 429):
|
||||
if response.status_code == 400:
|
||||
if factors['same_code'] != 400:
|
||||
|
||||
57
arjun/core/exporter.py
Normal file
57
arjun/core/exporter.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import json
|
||||
import re
|
||||
import requests
|
||||
|
||||
import arjun.core.config as mem
|
||||
from arjun.core.utils import populate
|
||||
|
||||
from arjun.core.utils import create_query_string
|
||||
|
||||
def json_export(result):
|
||||
"""
|
||||
exports result to a file in JSON format
|
||||
"""
|
||||
with open(mem.var['json_file'], 'w+', encoding='utf8') as json_output:
|
||||
json.dump(result, json_output, sort_keys=True, indent=4)
|
||||
|
||||
def burp_export(result):
|
||||
"""
|
||||
exports results to Burp Suite by sending request to Burp proxy
|
||||
"""
|
||||
for url, data in result.items():
|
||||
url = re.sub(r'://[^/]+', '://' + mem.var['burp_port'], url, 1)
|
||||
if data['method'] == 'GET':
|
||||
requests.get(url, params=populate(data['params']), headers=data['headers'])
|
||||
elif data['method'] == 'POST':
|
||||
requests.post(url, data=populate(data['params']), headers=data['headers'])
|
||||
elif data['method'] == 'JSON':
|
||||
requests.post(url, json=populate(data['params']), headers=data['headers'])
|
||||
|
||||
def text_export(result):
|
||||
"""
|
||||
exports results to a text file, one url per line
|
||||
"""
|
||||
with open(mem.var['text_file'], 'w+', encoding='utf8') as text_file:
|
||||
for url, data in result.items():
|
||||
clean_url = url.lstrip('/')
|
||||
if data['method'] == 'JSON':
|
||||
text_file.write(clean_url + '\t' + json.dumps(populate(data['params'])) + '\n')
|
||||
else:
|
||||
query_string = create_query_string(data['params'])
|
||||
if '?' in clean_url:
|
||||
query_string = query_string.replace('?', '&', 1)
|
||||
if data['method'] == 'GET':
|
||||
text_file.write(clean_url + query_string + '\n')
|
||||
elif data['method'] == 'POST':
|
||||
text_file.write(clean_url + '\t' + query_string + '\n')
|
||||
|
||||
def exporter(result):
|
||||
"""
|
||||
main exporter function that calls other export functions
|
||||
"""
|
||||
if mem.var['json_file']:
|
||||
json_export(result)
|
||||
if mem.var['text_file']:
|
||||
text_export(result)
|
||||
if mem.var['burp_port']:
|
||||
burp_export(result)
|
||||
@@ -1,5 +1,28 @@
|
||||
import re
|
||||
from core.utils import reader, parse_request
|
||||
|
||||
def reader(path, mode='string'):
|
||||
"""
|
||||
reads a file
|
||||
returns a string/array containing the content of the file
|
||||
"""
|
||||
with open(path, 'r', encoding='utf-8') as file:
|
||||
if mode == 'lines':
|
||||
return list(filter(None, [line.rstrip('\n') for line in file]))
|
||||
else:
|
||||
return ''.join([line for line in file])
|
||||
|
||||
def parse_request(string):
|
||||
"""
|
||||
parses http request
|
||||
returns dict
|
||||
"""
|
||||
result = {}
|
||||
match = re.search(r'(?:([a-zA-Z0-9]+) ([^ ]+) [^ ]+\n)?([\s\S]+\n)\n?([\s\S]+)?', string)
|
||||
result['method'] = match.group(1)
|
||||
result['path'] = match.group(2)
|
||||
result['headers'] = parse_headers(match.group(3))
|
||||
result['data'] = match.group(4)
|
||||
return result
|
||||
|
||||
burp_regex = re.compile(r'''(?m)^ <url><!\[CDATA\[(.+?)\]\]></url>
|
||||
<host ip="[^"]*">[^<]+</host>
|
||||
@@ -15,6 +38,10 @@ burp_regex = re.compile(r'''(?m)^ <url><!\[CDATA\[(.+?)\]\]></url>
|
||||
|
||||
|
||||
def burp_import(path):
|
||||
"""
|
||||
imports targets from burp suite
|
||||
returns list (of request objects)
|
||||
"""
|
||||
requests = []
|
||||
content = reader(path)
|
||||
matches = re.finditer(burp_regex, content)
|
||||
@@ -36,6 +63,10 @@ def burp_import(path):
|
||||
|
||||
|
||||
def urls_import(path, method, headers, include):
|
||||
"""
|
||||
imports urls from a newline delimited text file
|
||||
returns list (of request objects)
|
||||
"""
|
||||
requests = []
|
||||
urls = reader(path, mode='lines')
|
||||
for url in urls:
|
||||
@@ -49,10 +80,17 @@ def urls_import(path, method, headers, include):
|
||||
|
||||
|
||||
def request_import(path):
|
||||
"""
|
||||
imports request from a raw request file
|
||||
returns dict
|
||||
"""
|
||||
return parse_request(reader(path))
|
||||
|
||||
|
||||
def importer(path, method, headers, include):
|
||||
"""
|
||||
main importer function that calls other import functions
|
||||
"""
|
||||
with open(path, 'r', encoding='utf-8') as file:
|
||||
for line in file:
|
||||
if line.startswith('<?xml'):
|
||||
|
||||
@@ -2,6 +2,10 @@ import os
|
||||
import tempfile
|
||||
|
||||
def prompt(default=None):
|
||||
"""
|
||||
lets user paste input by opening a temp file in a text editor
|
||||
returns str (content of tmp file)
|
||||
"""
|
||||
editor = 'nano'
|
||||
with tempfile.NamedTemporaryFile(mode='r+') as tmpfile:
|
||||
if default:
|
||||
|
||||
@@ -1,31 +1,40 @@
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
import requests
|
||||
import warnings
|
||||
|
||||
import core.config as mem
|
||||
import arjun.core.config as mem
|
||||
|
||||
from arjun.core.utils import dict_to_xml
|
||||
|
||||
warnings.filterwarnings('ignore') # Disable SSL related warnings
|
||||
|
||||
def requester(request, payload={}):
|
||||
"""
|
||||
central function for making http requests
|
||||
returns str on error otherwise response object of requests library
|
||||
"""
|
||||
if 'include' in request and request['include']:
|
||||
payload.update(request['include'])
|
||||
if mem.var['stable']:
|
||||
mem.var['delay'] = random.choice(range(6, 12))
|
||||
time.sleep(mem.var['delay'])
|
||||
url = request['url']
|
||||
if 'Host' not in request['headers']:
|
||||
this_host = re.search(r'https?://([^/]+)', url).group(1)
|
||||
request['headers']['Host'] = this_host.split('@')[1] if '@' in this_host else this_host
|
||||
if mem.var['kill']:
|
||||
return 'killed'
|
||||
try:
|
||||
if request['method'] == 'GET':
|
||||
response = requests.get(url, params=payload, headers=request['headers'], verify=False, timeout=mem.var['timeout'])
|
||||
elif request['method'] == 'JSON':
|
||||
response = requests.post(url, json=json.dumps(payload), headers=request['headers'], verify=False, timeout=mem.var['timeout'])
|
||||
if mem.var['include'] and '$arjun$' in mem.var['include']:
|
||||
payload = mem.var['include'].replace('$arjun$', json.dumps(payload).rstrip('}').lstrip('{'))
|
||||
response = requests.post(url, data=payload, headers=request['headers'], verify=False, timeout=mem.var['timeout'])
|
||||
else:
|
||||
response = requests.post(url, json=payload, headers=request['headers'], verify=False, timeout=mem.var['timeout'])
|
||||
elif request['method'] == 'XML':
|
||||
payload = mem.var['include'].replace('$arjun$', dict_to_xml(payload))
|
||||
response = requests.post(url, data=payload, headers=request['headers'], verify=False, timeout=mem.var['timeout'])
|
||||
else:
|
||||
response = requests.post(url, data=payload, headers=request['headers'], verify=False, timeout=mem.var['timeout'])
|
||||
return response
|
||||
|
||||
@@ -4,13 +4,19 @@ import random
|
||||
import requests
|
||||
|
||||
import concurrent.futures
|
||||
from dicttoxml import dicttoxml
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from plugins.otx import otx
|
||||
from plugins.wayback import wayback
|
||||
from plugins.commoncrawl import commoncrawl
|
||||
from arjun.core.prompt import prompt
|
||||
from arjun.core.importer import importer
|
||||
|
||||
from arjun.plugins.otx import otx
|
||||
from arjun.plugins.wayback import wayback
|
||||
from arjun.plugins.commoncrawl import commoncrawl
|
||||
|
||||
import arjun.core.config as mem
|
||||
from arjun.core.colors import info
|
||||
|
||||
from core.colors import info
|
||||
|
||||
def lcs(s1, s2):
|
||||
"""
|
||||
@@ -31,7 +37,7 @@ def lcs(s1, s2):
|
||||
return s1[x_longest - longest: x_longest]
|
||||
|
||||
|
||||
def extractHeaders(headers):
|
||||
def extract_headers(headers):
|
||||
"""
|
||||
parses headers provided through command line
|
||||
returns dict
|
||||
@@ -93,44 +99,45 @@ def stable_request(url, headers):
|
||||
return None
|
||||
|
||||
|
||||
def removeTags(html):
|
||||
def remove_tags(html):
|
||||
"""
|
||||
removes all the html from a webpage source
|
||||
"""
|
||||
return re.sub(r'(?s)<.*?>', '', html)
|
||||
|
||||
|
||||
def lineComparer(response1, response2):
|
||||
def diff_map(body_1, body_2):
|
||||
"""
|
||||
compares two webpage and finds the non-matching lines
|
||||
creates a list of lines that are common between two multi-line strings
|
||||
returns list
|
||||
"""
|
||||
response1 = response1.split('\n')
|
||||
response2 = response2.split('\n')
|
||||
num = 0
|
||||
dynamicLines = []
|
||||
for line1, line2 in zip(response1, response2):
|
||||
if line1 != line2:
|
||||
dynamicLines.append(num)
|
||||
num += 1
|
||||
return dynamicLines
|
||||
sig = []
|
||||
lines_1, lines_2 = body_1.split('\n'), body_2.split('\n')
|
||||
for line_1, line_2 in zip(lines_1, lines_2):
|
||||
if line_1 == line_2:
|
||||
sig.append(line_1)
|
||||
return sig
|
||||
|
||||
|
||||
def randomString(n):
|
||||
def random_str(n):
|
||||
"""
|
||||
generates a random string of length n
|
||||
"""
|
||||
return ''.join(str(random.choice(range(10))) for i in range(n))
|
||||
|
||||
|
||||
def getParams(include):
|
||||
def get_params(include):
|
||||
"""
|
||||
loads parameters from JSON/query string
|
||||
"""
|
||||
params = {}
|
||||
if include:
|
||||
if include.startswith('{'):
|
||||
try:
|
||||
params = json.loads(str(include).replace('\'', '"'))
|
||||
return params
|
||||
except json.decoder.JSONDecodeError:
|
||||
return {}
|
||||
else:
|
||||
cleaned = include.split('?')[-1]
|
||||
parts = cleaned.split('&')
|
||||
@@ -143,6 +150,18 @@ def getParams(include):
|
||||
return params
|
||||
|
||||
|
||||
def create_query_string(params):
|
||||
"""
|
||||
creates a query string from a list of parameters
|
||||
returns str
|
||||
"""
|
||||
query_string = ''
|
||||
for param in params:
|
||||
pair = param + '=' + random_str(4)
|
||||
query_string += pair
|
||||
return '?' + query_string
|
||||
|
||||
|
||||
def reader(path, mode='string'):
|
||||
"""
|
||||
reads a file
|
||||
@@ -225,3 +244,50 @@ def fetch_params(host):
|
||||
page += 1
|
||||
print('%s Progress: %i%%' % (info, 100), end='\r')
|
||||
return params
|
||||
|
||||
|
||||
def prepare_requests(args):
|
||||
"""
|
||||
creates a list of request objects used by Arjun from targets given by user
|
||||
returns list (of targs)
|
||||
"""
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0',
|
||||
'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',
|
||||
'Connection': 'close',
|
||||
'Upgrade-Insecure-Requests': '1'
|
||||
}
|
||||
if type(headers) == bool:
|
||||
headers = extract_headers(prompt())
|
||||
elif type(headers) == str:
|
||||
headers = extract_headers(headers)
|
||||
if mem.var['method'] == 'JSON':
|
||||
headers['Content-type'] = 'application/json'
|
||||
if args.url:
|
||||
params = get_params(args.include)
|
||||
return {
|
||||
'url': args.url,
|
||||
'method': mem.var['method'],
|
||||
'headers': headers,
|
||||
'include': params
|
||||
}
|
||||
elif args.import_file:
|
||||
return importer(args.import_file, mem.var['method'], headers, args.include)
|
||||
return []
|
||||
|
||||
|
||||
def nullify(*args, **kwargs):
|
||||
"""
|
||||
a function that does nothing
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def dict_to_xml(dict_obj):
|
||||
"""
|
||||
converts dict to xml string
|
||||
returns str
|
||||
"""
|
||||
return dicttoxml(dict_obj, root=False, attr_type=False).decode('utf-8')
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
|
||||
@@ -1,35 +1,26 @@
|
||||
import re
|
||||
|
||||
from core.utils import extract_js
|
||||
from arjun.core.utils import extract_js
|
||||
|
||||
def is_not_junk(string):
|
||||
return re.match(r'^[A-Za-z0-9_]+$', string)
|
||||
|
||||
def heuristic(response, paramList):
|
||||
def insert_words(words, wordlist, found):
|
||||
if words:
|
||||
for var in words:
|
||||
if var not in found and is_not_junk(var):
|
||||
found.append(var)
|
||||
if var in wordlist:
|
||||
wordlist.remove(var)
|
||||
wordlist.insert(0, var)
|
||||
|
||||
def heuristic(response, wordlist):
|
||||
found = []
|
||||
inputs = re.findall(r'(?i)<input.+?name=["\']?([^"\'\s>]+)', response)
|
||||
if inputs:
|
||||
for inpName in inputs:
|
||||
if inpName not in found and is_not_junk(inpName):
|
||||
if inpName in paramList:
|
||||
paramList.remove(inpName)
|
||||
found.append(inpName)
|
||||
paramList.insert(0, inpName)
|
||||
insert_words(inputs, wordlist, found)
|
||||
for script in extract_js(response):
|
||||
emptyJSvars = re.findall(r'([^\s!=<>]+)\s*=\s*[\'"`][\'"`]', script)
|
||||
if emptyJSvars:
|
||||
for var in emptyJSvars:
|
||||
if var not in found and is_not_junk(var):
|
||||
found.append(var)
|
||||
if var in paramList:
|
||||
paramList.remove(var)
|
||||
paramList.insert(0, var)
|
||||
arrayJSnames = re.findall(r'([^\'"]+)[\'"]:\s?[\'"]', script)
|
||||
if arrayJSnames:
|
||||
for var in arrayJSnames:
|
||||
if var not in found and is_not_junk(var):
|
||||
found.append(var)
|
||||
if var in paramList:
|
||||
paramList.remove(var)
|
||||
paramList.insert(0, var)
|
||||
empty_vars = re.findall(r'([^\s!=<>]+)\s*=\s*[\'"`][\'"`]', script)
|
||||
insert_words(empty_vars, wordlist, found)
|
||||
map_keys = re.findall(r'([^\'"]+)[\'"]:\s?[\'"]', script)
|
||||
insert_words(map_keys, wordlist, found)
|
||||
return found
|
||||
|
||||
42
setup.py
Normal file
42
setup.py
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import io
|
||||
from setuptools import setup, find_packages
|
||||
from os import path
|
||||
this_directory = path.abspath(path.dirname(__file__))
|
||||
with io.open(path.join(this_directory, 'README.md'), encoding='utf-8') as f:
|
||||
desc = f.read()
|
||||
|
||||
setup(
|
||||
name='arjun',
|
||||
version=__import__('arjun').__version__,
|
||||
description='HTTP parameter discovery suite',
|
||||
long_description=desc,
|
||||
long_description_content_type='text/markdown',
|
||||
author='Somdev Sangwan',
|
||||
author_email='s0md3v@gmail.com',
|
||||
license='GNU General Public License v3 (GPLv3)',
|
||||
url='https://github.com/s0md3v/Arjun',
|
||||
download_url='https://github.com/s0md3v/Arjun/archive/v%s.zip' % __import__('arjun').__version__,
|
||||
packages=find_packages(),
|
||||
install_requires=[
|
||||
'requests',
|
||||
'dicttoxml'
|
||||
],
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: Information Technology',
|
||||
'Operating System :: OS Independent',
|
||||
'Topic :: Security',
|
||||
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'arjun = arjun.__main__:main'
|
||||
]
|
||||
},
|
||||
keywords=['arjun', 'bug bounty', 'http', 'pentesting', 'security'],
|
||||
)
|
||||
Reference in New Issue
Block a user