634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Arjun
7 |
8 |
9 |
10 | HTTP Parameter Discovery Suite
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 
28 |
29 | ### What's Arjun?
30 |
31 | Arjun can find query parameters for URL endpoints. If you don't get what that means, it's okay, read along.
32 |
33 | Web applications use parameters (or queries) to accept user input, take the following example into consideration
34 |
35 | `http://api.example.com/v1/userinfo?id=751634589`
36 |
37 | This URL seems to load user information for a specific user id, but what if there exists a parameter named `admin` which when set to `True` makes the endpoint provide more information about the user?\
38 | This is what Arjun does, it finds valid HTTP parameters with a huge default dictionary of 25,890 parameter names.
39 |
40 | The best part? It takes less than 10 seconds to go through this huge list while making just 50-60 requests to the target. [Here's how](https://github.com/s0md3v/Arjun/wiki/How-Arjun-works%3F).
41 |
42 | ### Why Arjun?
43 |
44 | - Supports `GET/POST/POST-JSON/POST-XML` requests
45 | - Automatically handles rate limits and timeouts
46 | - Export results to: BurpSuite, text or JSON file
47 | - Import targets from: BurpSuite, text file or a raw request file
48 | - Can passively extract parameters from JS or 3 external sources
49 |
50 | ### Installing Arjun
51 | The recommended way to install `arjun` is as following:
52 | ```
53 | pipx install arjun
54 | ```
55 | > Note: If you are using an old version of python, use pip instead of pipx.
56 |
57 | ### How to use Arjun?
58 |
59 | A detailed usage guide is available on [Usage](https://github.com/s0md3v/Arjun/wiki/Usage) section of the Wiki.
60 |
61 | Direct links to some basic options are given below:
62 |
63 | - [Scan a single URL](https://github.com/s0md3v/Arjun/wiki/Usage#scan-a-single-url)
64 | - [Import targets](https://github.com/s0md3v/Arjun/wiki/Usage#import-multiple-targets)
65 | - [Export results](https://github.com/s0md3v/Arjun/wiki/Usage#save-output-to-a-file)
66 | - [Use custom HTTP headers](https://github.com/s0md3v/Arjun/wiki/Usage#use-custom-http-headers)
67 |
68 | Optionally, you can use the `--help` argument to explore Arjun on your own.
69 |
70 | ##### Credits
71 | The parameter names wordlist is created by extracting top parameter names from [CommonCrawl](http://commoncrawl.org) dataset and merging best words from [SecLists](https://github.com/danielmiessler/SecLists) and [param-miner](https://github.com/PortSwigger/param-miner) wordlists into that.\
72 | `db/special.json` wordlist is taken from [data-payloads](https://github.com/yehgdotnet/data-payloads).
73 |
--------------------------------------------------------------------------------
/arjun/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = '2.2.7'
2 |
--------------------------------------------------------------------------------
/arjun/__main__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | from arjun.core.colors import green, end, info, bad, good, run, res
5 |
6 | import argparse
7 | import json
8 |
9 | from urllib.parse import urlparse
10 | import arjun.core.config as mem
11 | from arjun.core.exporter import exporter
12 | from arjun.core.anomaly import define, compare
13 | from arjun.core.utils import fetch_params, stable_request, random_str, slicer, confirm, populate, reader, nullify, prepare_requests, compatible_path
14 |
15 | from arjun.plugins.heuristic import heuristic
16 | from arjun.plugins.wl import detect_casing, covert_to_case
17 |
18 | arjun_dir = compatible_path(mem.__file__.replace(compatible_path('/core/config.py'), ''))
19 |
20 | parser = argparse.ArgumentParser() # defines the parser
21 | # Arguments that can be supplied
22 | parser.add_argument('-u', help='Target URL', dest='url')
23 | parser.add_argument('-o', '-oJ', help='Path for json output file.', dest='json_file')
24 | parser.add_argument('-oT', help='Path for text output file.', dest='text_file')
25 | 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')
26 | parser.add_argument('-d', help='Delay between requests in seconds. (default: 0)', dest='delay', type=float, default=0)
27 | parser.add_argument('-t', help='Number of concurrent threads. (default: 5)', dest='threads', type=int, default=5)
28 | parser.add_argument('-w', help='Wordlist file path. (default: {arjundir}/db/large.txt)', dest='wordlist', default=arjun_dir+'/db/large.txt')
29 | parser.add_argument('-m', help='Request method to use: GET/POST/XML/JSON. (default: GET)', dest='method', default='GET')
30 | parser.add_argument('-i', help='Import target URLs from file.', dest='import_file', nargs='?', const=True)
31 | parser.add_argument('-T', help='HTTP request timeout in seconds. (default: 15)', dest='timeout', type=float, default=15)
32 | parser.add_argument('-c', help='Chunk size. The number of parameters to be sent at once', type=int, dest='chunks', default=250)
33 | parser.add_argument('-q', help='Quiet mode. No output.', dest='quiet', action='store_true')
34 | 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)
35 | parser.add_argument('--headers', help='Add headers. Separate multiple headers with a new line.', dest='headers', nargs='?', const=True)
36 | parser.add_argument('--passive', help='Collect parameter names from passive sources like wayback, commoncrawl and otx.', dest='passive', nargs='?', const='-')
37 | parser.add_argument('--stable', help='Prefer stability over speed.', dest='stable', action='store_true')
38 | parser.add_argument('--include', help='Include this data in every request.', dest='include', default={})
39 | parser.add_argument('--disable-redirects', help='disable redirects', dest='disable_redirects', action='store_true')
40 | parser.add_argument('--casing', help='casing style for params e.g. like_this, likeThis, likethis', dest='casing')
41 | args = parser.parse_args() # arguments to be parsed
42 |
43 | if args.quiet:
44 | print = nullify
45 |
46 | print('''%s _
47 | /_| _ '
48 | ( |/ /(//) v%s
49 | _/ %s
50 | ''' % (green, __import__('arjun').__version__, end))
51 |
52 | try:
53 | from concurrent.futures import ThreadPoolExecutor, as_completed
54 | except ImportError:
55 | print('%s Please use Python > 3.2 to run Arjun.' % bad)
56 | quit()
57 |
58 | mem.var = vars(args)
59 |
60 | mem.var['method'] = mem.var['method'].upper()
61 |
62 | if mem.var['method'] != 'GET':
63 | mem.var['chunks'] = 500
64 |
65 | if mem.var['stable'] or mem.var['delay']:
66 | mem.var['threads'] = 1
67 | if mem.var['wordlist'] in ('large', 'medium', 'small'):
68 | mem.var['wordlist'] = f'{arjun_dir}/db/{mem.var["wordlist"]}.txt'
69 |
70 | try:
71 | wordlist_file = arjun_dir + '/db/small.txt' if args.wordlist == 'small' else args.wordlist
72 | wordlist_file = compatible_path(wordlist_file)
73 | wordlist = set(reader(wordlist_file, mode='lines'))
74 | if mem.var['passive']:
75 | host = mem.var['passive']
76 | if host == '-':
77 | host = urlparse(args.url).netloc
78 | print('%s Collecting parameter names from passive sources for %s, it may take a while' % (run, host))
79 | passive_params = fetch_params(host)
80 | wordlist.update(passive_params)
81 | print('%s Collected %s parameters, added to the wordlist' % (info, len(passive_params)))
82 | if args.casing:
83 | delimiter, casing = detect_casing(args.casing)
84 | wordlist = [covert_to_case(word, delimiter, casing) for word in wordlist]
85 | else:
86 | wordlist = list(wordlist)
87 | except FileNotFoundError:
88 | exit('%s The specified file for parameters doesn\'t exist' % bad)
89 |
90 | if len(wordlist) < mem.var['chunks']:
91 | mem.var['chunks'] = int(len(wordlist)/2)
92 |
93 | if not args.url and not args.import_file:
94 | exit('%s No target(s) specified' % bad)
95 |
96 | from arjun.core.requester import requester
97 | from arjun.core.bruter import bruter
98 |
99 | def narrower(request, factors, param_groups):
100 | """
101 | takes a list of parameters and narrows it down to parameters that cause anomalies
102 | returns list
103 | """
104 | anomalous_params = []
105 | threadpool = ThreadPoolExecutor(max_workers=mem.var['threads'])
106 | futures = (threadpool.submit(bruter, request, factors, params) for params in param_groups)
107 | for i, result in enumerate(as_completed(futures)):
108 | if result.result():
109 | anomalous_params.extend(slicer(result.result()))
110 | if mem.var['kill']:
111 | return anomalous_params
112 | print('%s Processing chunks: %i/%-6i' % (info, i + 1, len(param_groups)), end='\r')
113 | return anomalous_params
114 |
115 |
116 | def initialize(request, wordlist, single_url=False):
117 | """
118 | handles parameter finding process for a single request object
119 | returns 'skipped' (on error), list on success
120 | """
121 | url = request['url']
122 | if not url.startswith('http'):
123 | print('%s %s is not a valid URL' % (bad, url))
124 | return 'skipped'
125 | print('%s Probing the target for stability' % run)
126 | request['url'] = stable_request(url, request['headers'])
127 | mem.var['healthy_url'] = True
128 | if not request['url']:
129 | return 'skipped'
130 | else:
131 | fuzz = "z" + random_str(6)
132 | response_1 = requester(request, {fuzz[:-1]: fuzz[::-1][:-1]})
133 | if(isinstance(response_1, str)):
134 | return 'skipped'
135 | mem.var['healthy_url'] = response_1.status_code not in (400, 413, 418, 429, 503)
136 | if not mem.var['healthy_url']:
137 | print('%s Target returned HTTP %i, this may cause problems.' % (bad, response_1.status_code))
138 | if single_url:
139 | print('%s Analysing HTTP response for anomalies' % run)
140 | response_2 = requester(request, {fuzz[:-1]: fuzz[::-1][:-1]})
141 | if type(response_1) == str or type(response_2) == str:
142 | return 'skipped'
143 |
144 | # params from response must be extracted before factors but displayed later
145 | found, words_exist = heuristic(response_1, wordlist)
146 |
147 | factors = define(response_1, response_2, fuzz, fuzz[::-1], wordlist)
148 | zzuf = "z" + random_str(6)
149 | response_3 = requester(request, {zzuf[:-1]: zzuf[::-1][:-1]})
150 | while True:
151 | reason = compare(response_3, factors, {zzuf[:-1]: zzuf[::-1][:-1]})[2]
152 | if not reason:
153 | break
154 | factors[reason] = None
155 | if found:
156 | num = len(found)
157 | if words_exist:
158 | print('%s Extracted %i parameters from response for testing' % (good, num))
159 | else:
160 | s = 's' if num > 1 else ''
161 | print('%s Extracted %i parameter%s from response for testing: %s' % (good, num, s, ', '.join(found)))
162 | if single_url:
163 | print('%s Logicforcing the URL endpoint' % run)
164 | populated = populate(wordlist)
165 | with open(f'{arjun_dir}/db/special.json', 'r') as f:
166 | populated.update(json.load(f))
167 | param_groups = slicer(populated, int(len(wordlist)/mem.var['chunks']))
168 | prev_chunk_count = len(param_groups)
169 | last_params = []
170 | while True:
171 | param_groups = narrower(request, factors, param_groups)
172 | if len(param_groups) > prev_chunk_count:
173 | response_3 = requester(request, {zzuf[:-1]: zzuf[::-1][:-1]})
174 | if compare(response_3, factors, {zzuf[:-1]: zzuf[::-1][:-1]})[0] != '':
175 | print('%s Webpage is returning different content on each request. Skipping.' % bad)
176 | return []
177 | if mem.var['kill']:
178 | return 'skipped'
179 | param_groups = confirm(param_groups, last_params)
180 | prev_chunk_count = len(param_groups)
181 | if not param_groups:
182 | break
183 | confirmed_params = []
184 | for param in last_params:
185 | reason = bruter(request, factors, param, mode='verify')
186 | if reason:
187 | name = list(param.keys())[0]
188 | confirmed_params.append(name)
189 | if single_url:
190 | print('%s parameter detected: %s, based on: %s' % (res, name, reason))
191 | return confirmed_params
192 |
193 |
194 | def main():
195 | requests = prepare_requests(args)
196 |
197 | final_result = {}
198 | is_single = False if args.import_file else True
199 |
200 | try:
201 | mem.var['kill'] = False
202 | count = 0
203 | for request in requests:
204 | url = request['url']
205 | print('%s Scanning %d/%d: %s' % (run, count, len(requests), url))
206 | these_params = initialize(request, wordlist, single_url=is_single)
207 | count += 1
208 | mem.var['kill'] = False
209 | mem.var['bad_req_count'] = 0
210 | if these_params == 'skipped':
211 | print('%s Skipped %s due to errors' % (bad, url))
212 | elif these_params:
213 | final_result[url] = {}
214 | final_result[url]['params'] = these_params
215 | final_result[url]['method'] = request['method']
216 | final_result[url]['headers'] = request['headers']
217 | exporter(final_result)
218 | print('%s Parameters found: %-4s\n' % (good, ', '.join(final_result[url]['params'])))
219 | if not mem.var['json_file']:
220 | final_result = {}
221 | continue
222 | else:
223 | print('%s No parameters were discovered.\n' % info)
224 | except KeyboardInterrupt:
225 | exit()
226 |
227 |
228 | if __name__ == '__main__':
229 | main()
230 |
--------------------------------------------------------------------------------
/arjun/core/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s0md3v/Arjun/d1fb995cb1e064d4e171d83f19f6af79b0a3c5ce/arjun/core/__init__.py
--------------------------------------------------------------------------------
/arjun/core/anomaly.py:
--------------------------------------------------------------------------------
1 | import re
2 | import requests
3 |
4 | import arjun.core.config as mem
5 |
6 | from urllib.parse import urlparse
7 | from arjun.core.utils import diff_map, remove_tags
8 |
9 |
10 | def define(response_1, response_2, param, value, wordlist):
11 | """
12 | defines a rule list for detecting anomalies by comparing two HTTP response
13 | returns dict
14 | """
15 | factors = {
16 | 'same_code': None, # if http status code is same, contains that code
17 | 'same_body': None, # if http body is same, contains that body
18 | 'same_plaintext': None, # if http body isn't same but is same after removing html, contains that non-html text
19 | 'lines_num': None, # if number of lines in http body is same, contains that number
20 | 'lines_diff': None, # if http-body or plaintext aren't and there are more than two lines, contain which lines are same
21 | 'same_headers': None, # if the headers are same, contains those headers
22 | 'same_redirect': None, # if both requests redirect in similar manner, contains that redirection
23 | 'param_missing': None, # if param name is missing from the body, contains words that are already there
24 | 'value_missing': None # contains whether param value is missing from the body
25 | }
26 | if type(response_1) == type(response_2) == requests.models.Response:
27 | body_1, body_2 = response_1.text, response_2.text
28 | if response_1.status_code == response_2.status_code:
29 | factors['same_code'] = response_1.status_code
30 | if response_1.headers.keys() == response_2.headers.keys():
31 | factors['same_headers'] = list(response_1.headers.keys())
32 | factors['same_headers'].sort()
33 | if mem.var['disable_redirects']:
34 | if response_1.headers.get('Location', '') == response_2.headers.get('Location', ''):
35 | factors['same_redirect'] = urlparse(response_1.headers.get('Location', '')).path
36 | elif urlparse(response_1.url).path == urlparse(response_2.url).path:
37 | factors['same_redirect'] = urlparse(response_1.url).path
38 | else:
39 | factors['same_redirect'] = ''
40 | if response_1.text == response_2.text:
41 | factors['same_body'] = response_1.text
42 | elif response_1.text.count('\n') == response_2.text.count('\n'):
43 | factors['lines_num'] = response_1.text.count('\n')
44 | elif remove_tags(body_1) == remove_tags(body_2):
45 | factors['same_plaintext'] = remove_tags(body_1)
46 | elif body_1 and body_2 and body_1.count('\\n') == body_2.count('\\n'):
47 | factors['lines_diff'] = diff_map(body_1, body_2)
48 | if param not in response_2.text:
49 | factors['param_missing'] = [word for word in wordlist if word in response_2.text]
50 | if value not in response_2.text:
51 | factors['value_missing'] = True
52 | return factors
53 |
54 |
55 | def compare(response, factors, params):
56 | """
57 | detects anomalies by comparing a HTTP response against a rule list
58 | returns string, list (anomaly, list of parameters that caused it)
59 | """
60 | if response == '' or type(response) == str:
61 | return ('', [], '')
62 | these_headers = list(response.headers.keys())
63 | these_headers.sort()
64 | if factors['same_code'] is not None and response.status_code != factors['same_code']:
65 | return ('http code', params, 'same_code')
66 | if factors['same_headers'] is not None and these_headers != factors['same_headers']:
67 | return ('http headers', params, 'same_headers')
68 | if mem.var['disable_redirects']:
69 | if factors['same_redirect'] is not None and urlparse(response.headers.get('Location', '')).path != factors['same_redirect']:
70 | return ('redirection', params, 'same_redirect')
71 | elif factors['same_redirect'] is not None and 'Location' in response.headers:
72 | if urlparse(response.headers.get('Location', '')).path != factors['same_redirect']:
73 | return ('redirection', params, 'same_redirect')
74 | if factors['same_body'] is not None and response.text != factors['same_body']:
75 | return ('body length', params, 'same_body')
76 | if factors['lines_num'] is not None and response.text.count('\n') != factors['lines_num']:
77 | return ('number of lines', params, 'lines_num')
78 | if factors['same_plaintext'] is not None and remove_tags(response.text) != factors['same_plaintext']:
79 | return ('text length', params, 'same_plaintext')
80 | if factors['lines_diff'] is not None:
81 | for line in factors['lines_diff']:
82 | if line not in response.text:
83 | return ('lines', params, 'lines_diff')
84 | if factors['param_missing'] is not None:
85 | for param in params.keys():
86 | if len(param) < 5:
87 | continue
88 | if param not in factors['param_missing'] and re.search(r'[\'"\s]%s[\'"\s]' % re.escape(param), response.text):
89 | return ('param name reflection', params, 'param_missing')
90 | if factors['value_missing'] is not None:
91 | for value in params.values():
92 | if type(value) != str or len(value) != 6:
93 | continue
94 | if value in response.text and re.search(r'[\'"\s]%s[\'"\s]' % re.escape(value), response.text):
95 | return ('param value reflection', params, 'value_missing')
96 | return ('', [], '')
97 |
--------------------------------------------------------------------------------
/arjun/core/bruter.py:
--------------------------------------------------------------------------------
1 | import arjun.core.config as mem
2 |
3 | from arjun.core.anomaly import compare
4 | from arjun.core.requester import requester
5 | from arjun.core.error_handler import error_handler
6 |
7 |
8 | def bruter(request, factors, params, mode='bruteforce'):
9 | """
10 | returns anomaly detection result for a chunk of parameters
11 | returns list
12 | """
13 | if mem.var['kill']:
14 | return []
15 | response = requester(request, params)
16 | conclusion = error_handler(response, factors)
17 | if conclusion == 'retry':
18 | return bruter(request, factors, params, mode=mode)
19 | elif conclusion == 'kill':
20 | mem.var['kill'] = True
21 | return []
22 | comparison_result = compare(response, factors, params)
23 | if mode == 'verify':
24 | return comparison_result[0]
25 | return comparison_result[1]
26 |
--------------------------------------------------------------------------------
/arjun/core/colors.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | colors = True # Output should be colored
4 | machine = sys.platform # Detecting the os of current system
5 | if machine.lower().startswith(('os', 'win', 'darwin', 'ios')):
6 | colors = False # Colors shouldn't be displayed in mac & windows
7 | if not colors:
8 | white = green = red = yellow = end = back = info = que = bad = good = run = res = ''
9 | else:
10 | white = '\033[97m'
11 | green = '\033[92m'
12 | red = '\033[91m'
13 | yellow = '\033[93m'
14 | end = '\033[0m'
15 | back = '\033[7;91m'
16 | info = '\033[1;93m[!]\033[0m'
17 | que = '\033[1;94m[?]\033[0m'
18 | bad = '\033[1;91m[-]\033[0m'
19 | good = '\033[1;32m[+]\033[0m'
20 | run = '\033[1;97m[*]\033[0m'
21 | res = '\033[1;92m[✓]\033[0m'
22 |
--------------------------------------------------------------------------------
/arjun/core/config.py:
--------------------------------------------------------------------------------
1 | var = {} # all the cli arguments are added to this variable to be accessed globally
2 |
--------------------------------------------------------------------------------
/arjun/core/error_handler.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | import arjun.core.config as mem
4 |
5 | from arjun.core.colors import bad
6 |
7 |
8 | def connection_refused():
9 | """
10 | checks if a request should be retried if the server refused connection
11 | returns str
12 | """
13 | if mem.var['stable']:
14 | print('%s Hit rate limit, stabilizing the connection' % bad)
15 | mem.var['kill'] = False
16 | time.sleep(30)
17 | return 'retry'
18 | print('%s Target has rate limiting in place, please use --stable switch' % bad)
19 | return 'kill'
20 |
21 |
22 | def error_handler(response, factors):
23 | """
24 | decides what to do after performing a HTTP request
25 | 'ok': continue normally
26 | 'retry': retry this request
27 | 'kill': stop processing this target
28 | returns str
29 | """
30 | if type(response) != str and response.status_code in (400, 413, 418, 429, 503):
31 | if not mem.var['healthy_url']:
32 | return 'ok'
33 | if response.status_code == 503:
34 | mem.var['kill'] = True
35 | print('%s Target is unable to process requests, try --stable switch' % bad)
36 | return 'kill'
37 | elif response.status_code in (429, 418):
38 | print('%s Target has a rate limit in place, try --stable switch' % bad)
39 | return 'kill'
40 | else:
41 | if factors['same_code'] != response.status_code:
42 | mem.var['bad_req_count'] = mem.var.get('bad_req_count', 0) + 1
43 | if mem.var['bad_req_count'] > 20:
44 | mem.var['kill'] = True
45 | print('%s Server received a bad request. Try decreasing the chunk size with -c option' % bad)
46 | return 'kill'
47 | else:
48 | return 'ok'
49 | else:
50 | if 'Timeout' in response:
51 | if mem.var['timeout'] > 20:
52 | mem.var['kill'] = True
53 | print('%s Connection timed out, unable to increase timeout further' % bad)
54 | print('%s Target might have a rate limit in place, try --stable switch' % bad)
55 | return 'kill'
56 | else:
57 | print('%s Connection timed out, increased timeout by 5 seconds' % bad)
58 | mem.var['timeout'] += 5
59 | return 'retry'
60 | elif 'ConnectionRefused' in response:
61 | return connection_refused()
62 | elif type(response) == str:
63 | if '\'' in response:
64 | print('%s Encountered an error: %s' % (bad, response.split('\'')[1]))
65 | return 'kill'
66 | return 'ok'
67 |
--------------------------------------------------------------------------------
/arjun/core/exporter.py:
--------------------------------------------------------------------------------
1 | import json
2 | import requests
3 |
4 | import arjun.core.config as mem
5 | from arjun.core.utils import populate
6 |
7 | from arjun.core.utils import create_query_string
8 |
9 |
10 | def json_export(result):
11 | """
12 | exports result to a file in JSON format
13 | """
14 | with open(mem.var['json_file'], 'w+', encoding='utf8') as json_output:
15 | json.dump(result, json_output, sort_keys=True, indent=4)
16 |
17 |
18 | def burp_export(result):
19 | """
20 | exports results to Burp Suite by sending request to Burp proxy
21 | """
22 | proxy = ('' if ':' in mem.var['burp_proxy'] else '127.0.0.1:') + mem.var['burp_proxy']
23 | proxies = {
24 | 'http': 'http://' + proxy,
25 | 'https': 'https://' + proxy
26 | }
27 | for url, data in result.items():
28 | if data['method'] == 'GET':
29 | requests.get(url, params=populate(data['params']), headers=data['headers'], proxies=proxies, verify=False)
30 | elif data['method'] == 'POST':
31 | requests.post(url, data=populate(data['params']), headers=data['headers'], proxies=proxies, verify=False)
32 | elif data['method'] == 'JSON':
33 | requests.post(url, json=populate(data['params']), headers=data['headers'], proxies=proxies, verify=False)
34 |
35 |
36 | def text_export(result):
37 | """
38 | exports results to a text file, one url per line
39 | """
40 | with open(mem.var['text_file'], 'a+', encoding='utf8') as text_file:
41 | for url, data in result.items():
42 | clean_url = url.lstrip('/')
43 | if data['method'] == 'JSON':
44 | text_file.write(clean_url + '\t' + json.dumps(populate(data['params'])) + '\n')
45 | else:
46 | query_string = create_query_string(data['params'])
47 | if '?' in clean_url:
48 | query_string = query_string.replace('?', '&', 1)
49 | if data['method'] == 'GET':
50 | text_file.write(clean_url + query_string + '\n')
51 | elif data['method'] == 'POST':
52 | text_file.write(clean_url + '\t' + query_string + '\n')
53 |
54 |
55 | def exporter(result):
56 | """
57 | main exporter function that calls other export functions
58 | """
59 | if mem.var['json_file']:
60 | json_export(result)
61 | if mem.var['text_file']:
62 | text_export(result)
63 | if mem.var['burp_proxy']:
64 | burp_export(result)
65 |
--------------------------------------------------------------------------------
/arjun/core/importer.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | burp_regex = re.compile(r'''(?m)^
4 | [^<]+
5 | [^<]*
6 | [^<]*
7 |
8 | .*
9 | (.*)
10 |
11 | ([^<]*)
12 | ([^<]*)
13 | ([^<]*)''')
14 |
15 |
16 | def reader(path, mode='string'):
17 | """
18 | reads a file
19 | returns a string/array containing the content of the file
20 | """
21 | with open(path, 'r', encoding='utf-8') as file:
22 | if mode == 'lines':
23 | return list(filter(None, [line.rstrip('\n') for line in file]))
24 | else:
25 | return ''.join([line for line in file])
26 |
27 |
28 | def parse_request(string):
29 | """
30 | parses http request
31 | returns dict
32 | """
33 | result = {}
34 | match = re.search(r'(?:([a-zA-Z0-9]+) ([^ ]+) [^ ]+\n)?([\s\S]+\n)\n?([\s\S]+)?', string)
35 | result['method'] = match.group(1)
36 | result['path'] = match.group(2)
37 | result['headers'] = parse_headers(match.group(3))
38 | result['url'] = 'http://' + result['headers']['Host'] + result['path']
39 | result['data'] = match.group(4)
40 | return result
41 |
42 |
43 | def parse_headers(string):
44 | """
45 | parses headers
46 | return dict
47 | """
48 | result = {}
49 | for line in string.split('\n'):
50 | if len(line) > 1:
51 | splitted = line.split(':')
52 | result[splitted[0]] = ':'.join(splitted[1:]).strip()
53 | return result
54 |
55 |
56 | def burp_import(path):
57 | """
58 | imports targets from burp suite
59 | returns list (of request objects)
60 | """
61 | requests = []
62 | content = reader(path)
63 | matches = re.finditer(burp_regex, content)
64 | for match in matches:
65 | request = parse_request(match.group(4))
66 | headers = request['headers']
67 | if match.group(7) in ('HTML', 'JSON'):
68 | requests.append({
69 | 'url': match.group(1),
70 | 'method': match.group(2),
71 | 'extension': match.group(3),
72 | 'headers': headers,
73 | 'include': request['data'],
74 | 'code': match.group(5),
75 | 'length': match.group(6),
76 | 'mime': match.group(7)
77 | })
78 | return requests
79 |
80 |
81 | def urls_import(path, method, headers, include):
82 | """
83 | imports urls from a newline delimited text file
84 | returns list (of request objects)
85 | """
86 | requests = []
87 | urls = reader(path, mode='lines')
88 | for url in urls:
89 | requests.append({
90 | 'url': url,
91 | 'method': method,
92 | 'headers': headers,
93 | 'data': include
94 | })
95 | return requests
96 |
97 |
98 | def request_import(path):
99 | """
100 | imports request from a raw request file
101 | returns list
102 | """
103 | result = []
104 | result.append(parse_request(reader(path)))
105 | return result
106 |
107 |
108 | def importer(path, method, headers, include):
109 | """
110 | main importer function that calls other import functions
111 | """
112 | with open(path, 'r', encoding='utf-8') as file:
113 | for line in file:
114 | if line.startswith('', '', html)
96 |
97 |
98 | def diff_map(body_1, body_2):
99 | """
100 | creates a list of lines that are common between two multi-line strings
101 | returns list
102 | """
103 | sig = []
104 | lines_1, lines_2 = body_1.split('\n'), body_2.split('\n')
105 | for line_1, line_2 in zip(lines_1, lines_2):
106 | if line_1 == line_2:
107 | sig.append(line_1)
108 | return sig
109 |
110 |
111 | def random_str(n):
112 | """
113 | generates a random string of length n
114 | returns a string containing only digits
115 | """
116 | return ''.join(str(random.choice(range(10))) for i in range(n))
117 |
118 |
119 | def get_params(include):
120 | """
121 | loads parameters from JSON/query string
122 | returns parameter dict
123 | """
124 | params = {}
125 | if include:
126 | if include.startswith('{'):
127 | try:
128 | params = json.loads(str(include).replace('\'', '"'))
129 | if type(params) != dict:
130 | return {}
131 | return params
132 | except json.decoder.JSONDecodeError:
133 | return {}
134 | else:
135 | cleaned = include.split('?')[-1]
136 | parts = cleaned.split('&')
137 | for part in parts:
138 | each = part.split('=')
139 | try:
140 | params[each[0]] = each[1]
141 | except IndexError:
142 | params = {}
143 | return params
144 |
145 |
146 | def create_query_string(params):
147 | """
148 | creates a query string from a list of parameters
149 | returns str
150 | """
151 | query_string = ''
152 | for param in params:
153 | pair = param + '=' + random_str(4) + '&'
154 | query_string += pair
155 | if query_string.endswith('&'):
156 | query_string = query_string[:-1]
157 | return '?' + query_string
158 |
159 |
160 | def reader(path, mode='string'):
161 | """
162 | reads a file
163 | returns a string/array containing the content of the file
164 | """
165 | with open(path, 'r', encoding='utf-8') as file:
166 | if mode == 'lines':
167 | return list(filter(None, [line.rstrip('\n') for line in file]))
168 | else:
169 | return ''.join([line for line in file])
170 |
171 |
172 | def extract_js(response):
173 | """
174 | extracts javascript from a given string
175 | """
176 | scripts = []
177 | for part in re.split('(?i)', part, maxsplit=2)
179 | if len(actual_parts) > 1:
180 | scripts.append(actual_parts[0])
181 | return scripts
182 |
183 |
184 | def parse_headers(string):
185 | """
186 | parses headers
187 | returns dict
188 | """
189 | result = {}
190 | for line in string.split('\n'):
191 | if len(line) > 1:
192 | splitted = line.split(':')
193 | result[splitted[0]] = ':'.join(splitted[1:]).strip()
194 | return result
195 |
196 |
197 | def parse_request(string):
198 | """
199 | parses http request
200 | returns dict
201 | """
202 | result = {}
203 | match = re.search(r'(?:([a-zA-Z0-9]+) ([^ ]+) [^ ]+\n)?([\s\S]+\n)\n?([\s\S]+)?', string)
204 | result['method'] = match.group(1)
205 | result['path'] = match.group(2)
206 | result['headers'] = parse_headers(match.group(3))
207 | result['data'] = match.group(4)
208 | return result
209 |
210 |
211 | def http_import(path):
212 | """
213 | parses http request from a file
214 | returns dict
215 | """
216 | return parse_request(reader(path))
217 |
218 |
219 | def fetch_params(host):
220 | """
221 | fetch parameters from passive sources
222 | returns list
223 | """
224 | available_plugins = {'commoncrawl': commoncrawl, 'otx': otx, 'wayback': wayback}
225 | page = 0
226 | progress = 0
227 | params = {}
228 | while len(available_plugins) > 0 and page <= 10:
229 | threadpool = concurrent.futures.ThreadPoolExecutor(max_workers=len(available_plugins))
230 | futures = (threadpool.submit(func, host, page) for func in available_plugins.values())
231 | for each in concurrent.futures.as_completed(futures):
232 | if progress < 98:
233 | progress += 3
234 | this_result = each.result()
235 | if not this_result[1]:
236 | progress += ((10 - page) * 10 / 3)
237 | del available_plugins[this_result[2]]
238 | if len(this_result[0]) > 1:
239 | if not params:
240 | params = this_result[0]
241 | else:
242 | params.update(this_result[0])
243 | print('%s Progress: %i%%' % (info, progress), end='\r')
244 | page += 1
245 | print('%s Progress: %i%%' % (info, 100), end='\r')
246 | return params
247 |
248 |
249 | def prepare_requests(args):
250 | """
251 | creates a list of request objects used by Arjun from targets given by user
252 | returns list (of targets)
253 | """
254 | headers = {
255 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:83.0) Gecko/20100101 Firefox/83.0',
256 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
257 | 'Accept-Language': 'en-US,en;q=0.5',
258 | 'Accept-Encoding': 'gzip, deflate',
259 | 'Connection': 'close',
260 | 'Upgrade-Insecure-Requests': '1'
261 | }
262 | result = []
263 | if type(args.headers) == str:
264 | headers = extract_headers(args.headers)
265 | elif args.headers:
266 | headers = extract_headers(prompt())
267 | if mem.var['method'] == 'JSON':
268 | headers['Content-type'] = 'application/json'
269 | if args.url:
270 | params = get_params(args.include)
271 | result.append(
272 | {
273 | 'url': args.url,
274 | 'method': mem.var['method'],
275 | 'headers': headers,
276 | 'include': params
277 | }
278 | )
279 | elif args.import_file:
280 | result = importer(args.import_file, mem.var['method'], headers, args.include)
281 | return result
282 |
283 |
284 | def nullify(*args, **kwargs):
285 | """
286 | a function that does nothing
287 | """
288 | pass
289 |
290 |
291 | def dict_to_xml(dict_obj):
292 | """
293 | converts dict to xml string
294 | returns str
295 | """
296 | return dicttoxml(dict_obj, root=False, attr_type=False).decode('utf-8')
297 |
298 |
299 | def compatible_path(path):
300 | """
301 | converts filepaths to be compatible with the host OS
302 | returns str
303 | """
304 | if sys.platform.lower().startswith('win'):
305 | return path.replace('/', '\\')
306 | return path
307 |
--------------------------------------------------------------------------------
/arjun/db/small.txt:
--------------------------------------------------------------------------------
1 | AID
2 | AspxAutoDetectCookieSupport
3 | C
4 | CFID
5 | CFTOKEN
6 | CHANNEL
7 | CODE
8 | FID
9 | GI_ID
10 | ID
11 | Id
12 | Itemid
13 | L
14 | N
15 | O
16 | ObjectPath
17 | Open
18 | P
19 | PAGEN_1
20 | PS
21 | PUT
22 | Page
23 | R2
24 | ReturnUrl
25 | ___from_store
26 | ___store
27 | _action
28 | _bc_fsnf
29 | _bqG
30 | _bqH
31 | _method
32 | _ref
33 | _tags_limit
34 | _wpnonce
35 | a
36 | ac
37 | access
38 | account
39 | act
40 | action
41 | activity
42 | add
43 | add-to-cart
44 | add_acc
45 | add_cat
46 | add_comment
47 | add_event
48 | add_file
49 | add_rule
50 | add_site
51 | add_tag
52 | add_to_wishlist
53 | add_txt
54 | add_type
55 | add_user
56 | addurl
57 | adm
58 | admin
59 | advanced
60 | after
61 | age
62 | ai1ec
63 | aid
64 | ajax
65 | album
66 | alias
67 | allow
68 | allowed
69 | allowopts
70 | alpha
71 | alt
72 | alter
73 | amount
74 | amp
75 | ampday
76 | ampmonth
77 | ampyear
78 | api
79 | api_key
80 | app
81 | arch
82 | area
83 | arg
84 | args
85 | article
86 | asins
87 | at
88 | attachment_id
89 | auth
90 | authcfg
91 | authentication
92 | authlvl
93 | author
94 | author_id
95 | auto
96 | autoplay
97 | b
98 | back
99 | backurl
100 | before
101 | begindate
102 | bid
103 | bind
104 | block
105 | bo_table
106 | board
107 | board_no
108 | bonus
109 | bool
110 | boolean
111 | brand
112 | browse
113 | byte
114 | bytes
115 | c
116 | cHash
117 | cPath
118 | cache
119 | cad
120 | caid
121 | caldate
122 | call
123 | callback
124 | case
125 | cat
126 | cat_id
127 | cate_no
128 | category
129 | categoryId
130 | category_id
131 | categoryid
132 | catid
133 | cc
134 | cfg
135 | cgid
136 | char
137 | check
138 | checkout
139 | checkout_url
140 | cid
141 | city
142 | citycode
143 | cli
144 | client
145 | client_id
146 | clone
147 | cmd
148 | code
149 | collection
150 | collectionIds
151 | color
152 | column
153 | columns
154 | combine
155 | command
156 | comment
157 | compare
158 | conf
159 | config
160 | confirm
161 | content
162 | contenttype
163 | context
164 | continue
165 | controller
166 | count
167 | country
168 | counts
169 | coupon
170 | create
171 | creators
172 | csid
173 | csrf_token
174 | ct
175 | currency
176 | custom
177 | d
178 | daemon
179 | data
180 | date
181 | day
182 | days
183 | db
184 | dbg
185 | dbn
186 | dbname
187 | debug
188 | delete
189 | deleteUser
190 | department
191 | desc
192 | descending
193 | description
194 | dest
195 | destination
196 | detail
197 | did
198 | diff
199 | digest
200 | dir
201 | direction
202 | disable
203 | disabled
204 | display
205 | display_group
206 | dms
207 | do
208 | doc
209 | doc_id
210 | docid
211 | document
212 | document_srl
213 | domain
214 | download
215 | dq
216 | dummy
217 | e
218 | edit
219 | edition
220 | ei
221 | elementor_library
222 | email
223 | emailto
224 | enable
225 | enddate
226 | ends
227 | entity
228 | errmsg
229 | error
230 | errormsg
231 | errorstr
232 | etal
233 | event
234 | exe
235 | exec
236 | execute
237 | expand
238 | export
239 | ext
240 | extension
241 | f
242 | facet
243 | facets
244 | fallback
245 | family
246 | fbclid
247 | feature
248 | federated_search_id
249 | feed
250 | fetch
251 | fi
252 | fid
253 | field
254 | fields
255 | file
256 | file_name
257 | file_url
258 | filename
259 | filter
260 | filter_0
261 | filter_1
262 | filter_10
263 | filter_11
264 | filter_2
265 | filter_3
266 | filter_4
267 | filter_5
268 | filter_6
269 | filter_7
270 | filter_8
271 | filter_9
272 | filter_by
273 | filter_field_1
274 | filter_field_10
275 | filter_field_11
276 | filter_field_12
277 | filter_field_13
278 | filter_field_2
279 | filter_field_3
280 | filter_field_4
281 | filter_field_5
282 | filter_field_6
283 | filter_field_7
284 | filter_field_8
285 | filter_field_9
286 | filter_relational_operator
287 | filter_relational_operator_0
288 | filter_relational_operator_1
289 | filter_relational_operator_10
290 | filter_relational_operator_11
291 | filter_relational_operator_2
292 | filter_relational_operator_3
293 | filter_relational_operator_4
294 | filter_relational_operator_5
295 | filter_relational_operator_6
296 | filter_relational_operator_7
297 | filter_relational_operator_8
298 | filter_relational_operator_9
299 | filter_type_1
300 | filter_type_10
301 | filter_type_11
302 | filter_type_12
303 | filter_type_13
304 | filter_type_2
305 | filter_type_3
306 | filter_type_4
307 | filter_type_5
308 | filter_type_6
309 | filter_type_7
310 | filter_type_8
311 | filter_type_9
312 | filter_value_1
313 | filter_value_10
314 | filter_value_11
315 | filter_value_12
316 | filter_value_13
317 | filter_value_2
318 | filter_value_3
319 | filter_value_4
320 | filter_value_5
321 | filter_value_6
322 | filter_value_7
323 | filter_value_8
324 | filter_value_9
325 | filtername
326 | filterquery
327 | filters
328 | filtertype
329 | filtertype_0
330 | filtertype_1
331 | filtertype_10
332 | filtertype_11
333 | filtertype_2
334 | filtertype_3
335 | filtertype_4
336 | filtertype_5
337 | filtertype_6
338 | filtertype_7
339 | filtertype_8
340 | filtertype_9
341 | fl
342 | flag
343 | fn
344 | fname
345 | focus
346 | folder
347 | folder_url
348 | font
349 | force
350 | format
351 | forum
352 | forward
353 | fp
354 | fq
355 | fr
356 | fr2
357 | from
358 | from_url
359 | fromdate
360 | func
361 | function
362 | g
363 | gallery
364 | geo
365 | gid
366 | go
367 | goto
368 | grant
369 | group
370 | group_id
371 | groups
372 | h
373 | hash
374 | height
375 | hideanons
376 | hidebots
377 | hideliu
378 | hideminor
379 | hidemyself
380 | highlight
381 | hl
382 | host
383 | hour
384 | html
385 | i
386 | id
387 | id_product
388 | ids
389 | idx
390 | idxno
391 | ie
392 | image
393 | image_url
394 | img
395 | img_url
396 | immagine
397 | import
398 | in
399 | inc
400 | include
401 | index
402 | info
403 | init
404 | input
405 | inside
406 | instance
407 | inverse
408 | ip
409 | ipaddr
410 | is_logged
411 | isbn
412 | islandora_solr_search_navigation
413 | ispublic
414 | item
415 | itemid
416 | items_per_page
417 | j
418 | job
419 | json
420 | jsonp
421 | jump
422 | k
423 | key
424 | keyword
425 | keywords
426 | kind
427 | kw
428 | l
429 | label
430 | labels
431 | lang
432 | language
433 | lat
434 | layout
435 | leave
436 | len
437 | length
438 | letter
439 | level
440 | levels
441 | license_id
442 | lid
443 | lightbox
444 | like_comment
445 | limit
446 | link
447 | list
448 | listStyle
449 | list_type
450 | lng
451 | load
452 | load_file
453 | load_url
454 | loc
455 | locale
456 | locale-attribute
457 | locate
458 | location
459 | log
460 | login
461 | login_url
462 | logout
463 | lon
464 | lookfor
465 | lr
466 | lvl
467 | m
468 | main_page
469 | make
470 | manualLanguageChange
471 | manufacturer
472 | map
473 | max
474 | maximum
475 | maxlen
476 | media
477 | medio
478 | menu
479 | method
480 | mid
481 | min
482 | mini
483 | minimum
484 | minute
485 | mod
486 | modal
487 | mode
488 | model
489 | modify
490 | module
491 | month
492 | msg
493 | n
494 | name
495 | names
496 | navigation
497 | neighborhood
498 | next
499 | next_page
500 | nid
501 | no
502 | no_cache
503 | no_renew
504 | nocache
505 | nofollow
506 | nonce
507 | ns
508 | null
509 | num
510 | number
511 | o
512 | object
513 | offset
514 | oid
515 | oldid
516 | op
517 | open
518 | operator
519 | option
520 | ord
521 | order
522 | order_by
523 | order_type
524 | orderby
525 | ordering
526 | org
527 | organization
528 | origin
529 | osCsid
530 | out
531 | output
532 | p
533 | p_auth
534 | p_p_col_count
535 | p_p_col_id
536 | p_p_id
537 | p_p_lifecycle
538 | p_p_mode
539 | p_p_state
540 | pag
541 | page
542 | pageId
543 | pageSize
544 | page_id
545 | page_url
546 | paged
547 | pageid
548 | pagina
549 | param
550 | params
551 | part
552 | password
553 | path
554 | payload
555 | pdf
556 | per
557 | per_page
558 | person
559 | pg
560 | php_path
561 | pid
562 | ping
563 | places
564 | platform
565 | plugin
566 | port
567 | pos
568 | position
569 | post
570 | post_id
571 | post_type
572 | postcount
573 | power
574 | pp
575 | pref
576 | prefix
577 | prefn1
578 | prefn2
579 | prefv1
580 | prefv2
581 | prepare
582 | prev
583 | preview
584 | previous_page_section_name
585 | price
586 | print
587 | printable
588 | process
589 | prod_id
590 | product
591 | product_count
592 | product_id
593 | product_no
594 | product_order
595 | product_orderby
596 | product_sort
597 | product_type
598 | product_view
599 | products_id
600 | profile
601 | promo_code
602 | provider
603 | ps
604 | publicationYear
605 | q
606 | qf
607 | qid
608 | qty
609 | quantity
610 | query
611 | quote
612 | r
613 | range
614 | range_end
615 | range_start
616 | rank
617 | ranking
618 | rate
619 | rating
620 | reaction
621 | read
622 | recor
623 | redir
624 | redirect
625 | redirect_to
626 | redirect_uri
627 | redirect_url
628 | redlink
629 | ref
630 | reference
631 | referer
632 | referrer
633 | refurl
634 | reg
635 | regex
636 | regexp
637 | region
638 | rename
639 | render
640 | reply
641 | replytocom
642 | report
643 | req
644 | res
645 | res_format
646 | reset
647 | response_type
648 | results
649 | retpath
650 | return
651 | returnTo
652 | returnUrl
653 | return_path
654 | return_to
655 | return_url
656 | returnto
657 | returntoquery
658 | returnurl
659 | rev
660 | revision
661 | rid
662 | role
663 | room
664 | root
665 | route
666 | row
667 | rows
668 | rpp
669 | rss
670 | rt
671 | run
672 | rurl
673 | s
674 | sa
675 | sale_date
676 | sc
677 | scope
678 | script
679 | sd
680 | search
681 | search_field
682 | search_type
683 | searchfilter
684 | searchid
685 | section
686 | sector
687 | security
688 | sel
689 | select
690 | selected_facets
691 | selected_filters
692 | sequence
693 | service
694 | session
695 | set
696 | sf_culture
697 | share
698 | shared
699 | shell
700 | show
701 | showComment
702 | showtopic
703 | site
704 | size
705 | sku
706 | sleep
707 | smode
708 | sort
709 | sortBy
710 | sortDir
711 | sort_by
712 | sort_index
713 | sort_key
714 | sort_order
715 | sortby
716 | sorting
717 | source
718 | source_id
719 | sp
720 | special
721 | sr
722 | src
723 | sso
724 | st
725 | star
726 | start
727 | starts_with
728 | state
729 | status
730 | step
731 | stop
732 | strict
733 | string
734 | style
735 | sub
736 | subcat
737 | subject
738 | subjects
739 | submit
740 | subscribe
741 | symbol
742 | sys
743 | t
744 | tab
745 | tab_details
746 | tab_files
747 | table
748 | tag
749 | tags
750 | target
751 | task
752 | tbn
753 | temp
754 | template
755 | term
756 | terms
757 | test
758 | testing
759 | text
760 | theme
761 | thread
762 | tid
763 | time
764 | timestamp
765 | tipo
766 | title
767 | tld
768 | tmp
769 | tmpl
770 | to
771 | todate
772 | toggle
773 | toggleopen
774 | token
775 | topLod
776 | topic
777 | tree
778 | type
779 | type_id
780 | u
781 | uid
782 | unsubscribe_token
783 | update
784 | upload
785 | uri
786 | url
787 | used
788 | user
789 | user_id
790 | userid
791 | username
792 | utm_campaign
793 | utm_content
794 | utm_medium
795 | utm_source
796 | utm_term
797 | utmi_p
798 | uuid
799 | v
800 | val
801 | validate
802 | value
803 | var
804 | variant
805 | ver
806 | version
807 | vid
808 | view
809 | view_all
810 | view_type
811 | volume
812 | vq
813 | w
814 | where
815 | widgetId
816 | widgetType
817 | width
818 | window
819 | withdraw
820 | word
821 | worker
822 | workerId
823 | wr_id
824 | write
825 | x
826 | xargs
827 | xdebug
828 | xhr
829 | xml
830 | y
831 | year
832 | yr
833 | z
834 | zip
835 | zoom
836 |
--------------------------------------------------------------------------------
/arjun/db/special.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug": "yes",
3 | "debug": "true",
4 | "debug": "1",
5 | "debug": "on",
6 | "test": "yes",
7 | "test": "true",
8 | "test": "1",
9 | "test": "on",
10 | "source": "yes",
11 | "source": "true",
12 | "source": "1",
13 | "source": "on",
14 | "admin": "yes",
15 | "admin": "true",
16 | "admin": "1",
17 | "admin": "on",
18 | "show": "yes",
19 | "show": "true",
20 | "show": "1",
21 | "show": "on",
22 | "bot": "yes",
23 | "bot": "1",
24 | "bot": "on",
25 | "antibot": "off",
26 | "antibot": "0",
27 | "antibot": "no",
28 | "antibot": "none",
29 | "antibot": "nil",
30 | "antirobot": "off",
31 | "antirobot": "0",
32 | "antirobot": "no",
33 | "antirobot": "none",
34 | "antirobot": "nil",
35 | "env": "staging",
36 | "env": "test",
37 | "env": "testing",
38 | "env": "pre",
39 | "env": "pre-staging",
40 | "env": "daily",
41 | "env": "uat",
42 | "anticrawl": "off",
43 | "anticrawl": "0",
44 | "anticrawl": "none",
45 | "anticrawl": "no",
46 | "anticrawl": "nil",
47 | "captcha": "off",
48 | "captcha": "0",
49 | "captcha": "none",
50 | "captcha": "no",
51 | "captcha": "nil",
52 | "signing": "off",
53 | "signing": "0",
54 | "signing": "none",
55 | "signing": "no",
56 | "signing": "nil",
57 | "signature": "off",
58 | "signature": "0",
59 | "signature": "none",
60 | "signature": "no",
61 | "signature": "nil",
62 | "enc": "off",
63 | "enc": "0",
64 | "enc": "none",
65 | "enc": "no",
66 | "enc": "nil",
67 | "encryption": "off",
68 | "encryption": "0",
69 | "encryption": "none",
70 | "encryption": "no",
71 | "encryption": "nil",
72 | "automation": "on",
73 | "automation": "1",
74 | "automation": "yes",
75 | "waf": "disabled",
76 | "waf": "disable",
77 | "waf": "off",
78 | "waf": "0",
79 | "waf": "no",
80 | "security": "disabled",
81 | "security": "disable",
82 | "security": "0",
83 | "security": "no",
84 | "isdebug": "yes",
85 | "isdebug": "true",
86 | "isdebug": "1",
87 | "isdebug": "on",
88 | "istest": "yes",
89 | "istest": "true",
90 | "istest": "1",
91 | "istest": "on",
92 | "isadmin": "yes",
93 | "isadmin": "true",
94 | "isadmin": "1",
95 | "isadmin": "on",
96 | "isbot": "yes",
97 | "isbot": "1",
98 | "isbot": "on",
99 | "isenv": "staging",
100 | "isenv": "test",
101 | "isenv": "testing",
102 | "isenv": "pre",
103 | "isenv": "pre-staging",
104 | "isenv": "daily",
105 | "isenv": "uat",
106 | "hascaptcha": "off",
107 | "hascaptcha": "0",
108 | "hascaptcha": "none",
109 | "hascaptcha": "no",
110 | "hascaptcha": "nil",
111 | "hassigning": "off",
112 | "hassigning": "0",
113 | "hassigning": "none",
114 | "hassigning": "no",
115 | "hassigning": "nil",
116 | "hassignature": "off",
117 | "hassignature": "0",
118 | "hassignature": "none",
119 | "hassignature": "no",
120 | "hassignature": "nil",
121 | "isenc": "off",
122 | "isenc": "0",
123 | "isenc": "none",
124 | "isenc": "no",
125 | "isenc": "nil",
126 | "isencryption": "off",
127 | "isencryption": "0",
128 | "isencryption": "none",
129 | "isencryption": "no",
130 | "isencryption": "nil",
131 | "hasautomation": "on",
132 | "hasautomation": "1",
133 | "hasautomation": "yes",
134 | "haswaf": "disabled",
135 | "haswaf": "disable",
136 | "haswaf": "off",
137 | "haswaf": "0",
138 | "haswaf": "no",
139 | "issecurity": "disabled",
140 | "issecurity": "disable",
141 | "hassecurity": "0",
142 | "hassecurity": "no",
143 | "disable": "waf",
144 | "disable": "security",
145 | "disabled": "waf",
146 | "disabled": "security",
147 | "dosinglesignon": "1",
148 | "singlesignon": "1",
149 | "hassinglesignon": "1",
150 | "dosso": "1",
151 | "sso": "1",
152 | "hassso": "1"
153 | }
--------------------------------------------------------------------------------
/arjun/plugins/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/s0md3v/Arjun/d1fb995cb1e064d4e171d83f19f6af79b0a3c5ce/arjun/plugins/__init__.py
--------------------------------------------------------------------------------
/arjun/plugins/commoncrawl.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | from urllib.parse import urlparse
4 |
5 |
6 | def commoncrawl(host, page=0):
7 | these_params = set()
8 | response = requests.get('http://index.commoncrawl.org/CC-MAIN-2024-42-index?url=*.%s&fl=url&page=%s&limit=10000' % (host, page), verify=False).text
9 | if response.startswith(''):
10 | return ([], False, 'commoncrawl')
11 | urls = response.split('\n')
12 | for url in urls:
13 | for param in urlparse(url).query.split('&'):
14 | these_params.add(param.split('=')[0])
15 | return (these_params, True, 'commoncrawl')
16 |
--------------------------------------------------------------------------------
/arjun/plugins/heuristic.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | from arjun.core.colors import info
4 | import arjun.core.config as mem
5 | from arjun.core.utils import extract_js
6 |
7 | # TODO: for map keys, javascript tolerates { param: "value" }
8 | re_words = re.compile(r'[A-Za-z][A-Za-z0-9_]*')
9 | re_not_junk = re.compile(r'^[A-Za-z0-9_]+$')
10 | re_inputs = re.compile(r'''(?i)<(?:input|textarea)[^>]+?(?:id|name)=["']?([^"'\s>]+)''')
11 | re_empty_vars = re.compile(r'''(?:[;\n]|\bvar|\blet)(\w+)\s*=\s*(?:['"`]{1,2}|true|false|null)''')
12 | re_map_keys = re.compile(r'''['"](\w+?)['"]\s*:\s*['"`]''')
13 |
14 |
15 | def is_not_junk(param):
16 | return (re_not_junk.match(param) is not None)
17 |
18 |
19 | def heuristic(raw_response, wordlist):
20 | words_exist = False
21 | potential_params = []
22 |
23 | headers, response = raw_response.headers, raw_response.text
24 | if headers.get('content-type', '').startswith(('application/json', 'text/plain')):
25 | if len(response) < 200:
26 | if ('required' or 'missing' or 'not found' or 'requires') in response.lower() and ('param' or 'parameter' or 'field') in response.lower():
27 | if not mem.var['quiet']:
28 | print('%s The endpoint seems to require certain parameters to function. Check the response and use the --include option appropriately for better results.' % info)
29 | words_exist = True
30 | potential_params = re_words.findall(response)
31 | # Parse Inputs
32 | input_names = re_inputs.findall(response)
33 | potential_params += input_names
34 |
35 | # Parse Scripts
36 | for script in extract_js(response):
37 | empty_vars = re_empty_vars.findall(script)
38 | potential_params += empty_vars
39 |
40 | map_keys = re_map_keys.findall(script)
41 | potential_params += map_keys
42 |
43 | if len(potential_params) == 0:
44 | return [], words_exist
45 |
46 | found = set()
47 | for word in potential_params:
48 | if is_not_junk(word) and (word not in found):
49 | found.add(word)
50 |
51 | if word in wordlist:
52 | wordlist.remove(word)
53 | wordlist.insert(0, word)
54 |
55 | return list(found), words_exist
56 |
--------------------------------------------------------------------------------
/arjun/plugins/otx.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | from urllib.parse import urlparse
4 |
5 |
6 | def otx(host, page):
7 | these_params = set()
8 | data = requests.get('https://otx.alienvault.com/api/v1/indicators/hostname/%s/url_list?limit=50&page=%d' % (host, page+1), verify=False).json()
9 | if 'url_list' not in data:
10 | return (these_params, False, 'otx')
11 | for obj in data['url_list']:
12 | for param in urlparse(obj['url']).query.split('&'):
13 | these_params.add(param.split('=')[0])
14 | return (these_params, data['has_next'], 'otx')
15 |
--------------------------------------------------------------------------------
/arjun/plugins/wayback.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 | from urllib.parse import urlparse
4 |
5 |
6 | def wayback(host, page):
7 | payload = {
8 | 'url': host,
9 | 'matchType': 'host',
10 | 'collapse': 'urlkey',
11 | 'fl': 'original',
12 | 'page': page,
13 | 'limit': 10000
14 | }
15 | headers = {
16 | 'User-Agent': 'Mozilla'
17 | }
18 | try:
19 | these_params = set()
20 | response = requests.get(
21 | 'http://web.archive.org/cdx/search?filter=mimetype:text/html&filter=statuscode:200',
22 | params=payload,
23 | headers=headers,
24 | verify=False
25 | ).text
26 | if not response:
27 | return (these_params, False, 'wayback')
28 | urls = filter(None, response.split('\n'))
29 | for url in urls:
30 | for param in urlparse(url).query.split('&'):
31 | these_params.add(param.split('=')[0])
32 | return (these_params, True, 'wayback')
33 | except requests.exceptions.ConnectionError:
34 | return (these_params, False, 'wayback')
35 |
--------------------------------------------------------------------------------
/arjun/plugins/wl.py:
--------------------------------------------------------------------------------
1 | def detect_casing(string):
2 | """Detect the casing style and delimiter of given string."""
3 | delimiter = ""
4 | casing = ""
5 |
6 | if string.islower():
7 | casing = "l"
8 | elif string.isupper():
9 | casing = "u"
10 | else:
11 | casing = casing = "c" if string[0].islower() else "p"
12 |
13 | if "-" in string:
14 | delimiter = "-"
15 | elif "_" in string:
16 | delimiter = "_"
17 | elif "." in string:
18 | delimiter = "."
19 |
20 | return delimiter, casing
21 |
22 |
23 | def transform(parts, delimiter, casing):
24 | """Combine list of strings to form a string with given casing style."""
25 | if len(parts) == 1:
26 | if casing == "l":
27 | return parts[0].lower()
28 | elif casing == "u":
29 | return parts[0].upper()
30 | return parts[0]
31 |
32 | result = []
33 | for i, part in enumerate(parts):
34 | if casing == "l":
35 | transformed = part.lower()
36 | elif casing == "u":
37 | transformed = part.upper()
38 | elif casing == "c":
39 | if i == 0:
40 | transformed = part.lower()
41 | else:
42 | transformed = part.lower().title()
43 | else: # casing == "p"
44 | transformed = part.lower().title()
45 |
46 | result.append(transformed)
47 |
48 | return delimiter.join(result)
49 |
50 |
51 | def handle(text):
52 | """Break down a string into array of 'words'."""
53 | if "-" in text:
54 | return text.split("-")
55 | elif "_" in text:
56 | return text.split("_")
57 | elif "." in text:
58 | return text.split(".")
59 |
60 | if not text.islower() and not text.isupper():
61 | parts = []
62 | temp = ""
63 | for char in text:
64 | if not char.isupper():
65 | temp += char
66 | else:
67 | if temp:
68 | parts.append(temp)
69 | temp = char
70 | if temp:
71 | parts.append(temp)
72 | return parts
73 |
74 | return [text]
75 |
76 |
77 | def covert_to_case(string, delimiter, casing):
78 | """Process input stream and write transformed text to output stream."""
79 | parts = handle(string)
80 | return transform(parts, delimiter, casing)
81 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import io
5 | from setuptools import setup, find_packages
6 | from os import path
7 | this_directory = path.abspath(path.dirname(__file__))
8 | with io.open(path.join(this_directory, 'README.md'), encoding='utf-8') as f:
9 | desc = f.read()
10 |
11 | setup(
12 | name='arjun',
13 | version=__import__('arjun').__version__,
14 | description='HTTP parameter discovery suite',
15 | long_description=desc,
16 | long_description_content_type='text/markdown',
17 | author='Somdev Sangwan',
18 | author_email='s0md3v@gmail.com',
19 | license='GNU General Public License v3 (GPLv3)',
20 | url='https://github.com/s0md3v/Arjun',
21 | download_url='https://github.com/s0md3v/Arjun/archive/v%s.zip' % __import__('arjun').__version__,
22 | zip_safe=False,
23 | packages=find_packages(),
24 | package_data={'arjun': ['db/*']},
25 | install_requires=[
26 | 'requests',
27 | 'dicttoxml',
28 | 'ratelimit'
29 | ],
30 | classifiers=[
31 | 'Development Status :: 5 - Production/Stable',
32 | 'Intended Audience :: Developers',
33 | 'Intended Audience :: Information Technology',
34 | 'Operating System :: OS Independent',
35 | 'Topic :: Security',
36 | 'License :: OSI Approved :: GNU Affero General Public License v3',
37 | 'Programming Language :: Python :: 3.4',
38 | ],
39 | entry_points={
40 | 'console_scripts': [
41 | 'arjun = arjun.__main__:main'
42 | ]
43 | },
44 | keywords=['arjun', 'bug bounty', 'http', 'pentesting', 'security'],
45 | )
46 |
--------------------------------------------------------------------------------