Skip to content

Commit 4a0931d

Browse files
authored
Merge pull request #172 from s0md3v/2.2.0
2.2.0
2 parents 4219512 + 289f546 commit 4a0931d

9 files changed

+198
-21
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
#### 2.2.0
2+
- Ability to detect parameters that respond to a certain value e.g. "?debug=yes"
3+
- Added "required parameter" detection
4+
- Heuristic can now extract words out of json/text responses
5+
- Fixed -oB option description
6+
17
#### 2.1.6
28
- Fixed multiple breaking bugs
39
- Export results as they come in multi-target mode

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,5 @@ Direct links to some basic options are given below:
7474
Optionally, you can use the `--help` argument to explore Arjun on your own.
7575

7676
##### Credits
77-
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.
77+
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.\
78+
`db/special.json` wordlist is taken from [data-payloads](https://github.com/yehgdotnet/data-payloads).

arjun/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '2.1.6'
1+
__version__ = '2.2.0'

arjun/__main__.py

+14-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from arjun.core.colors import green, end, info, bad, good, run, res
55

66
import argparse
7+
import json
78

89
from urllib.parse import urlparse
910
import arjun.core.config as mem
@@ -26,7 +27,7 @@
2627
parser.add_argument('-d', help='Delay between requests in seconds. (default: 0)', dest='delay', type=float, default=0)
2728
parser.add_argument('-t', help='Number of concurrent threads. (default: 5)', dest='threads', type=int, default=5)
2829
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('-m', help='Request method to use: GET/POST/XML/JSON/HEADERS. (default: GET)', dest='method', default='GET')
3031
parser.add_argument('-i', help='Import target URLs from file.', dest='import_file', nargs='?', const=True)
3132
parser.add_argument('-T', help='HTTP request timeout in seconds. (default: 15)', dest='timeout', type=float, default=15)
3233
parser.add_argument('-c', help='Chunk size. The number of parameters to be sent at once', type=int, dest='chunks', default=500)
@@ -127,14 +128,19 @@ def initialize(request, wordlist, single_url=False):
127128
factors = define(response_1, response_2, fuzz, fuzz[::-1], wordlist)
128129
if single_url:
129130
print('%s Analysing HTTP response for potential parameter names' % run)
130-
found = heuristic(response_1.text, wordlist)
131+
found, words_exist = heuristic(response_1, wordlist)
131132
if found:
132133
num = len(found)
133-
s = 's' if num > 1 else ''
134-
print('%s Heuristic scanner found %i parameter%s: %s' % (good, num, s, ', '.join(found)))
134+
if words_exist:
135+
print('%s Heuristic scanner found %i parameters' % (good, num))
136+
else:
137+
s = 's' if num > 1 else ''
138+
print('%s Heuristic scanner found %i parameter%s: %s' % (good, num, s, ', '.join(found)))
135139
if single_url:
136140
print('%s Logicforcing the URL endpoint' % run)
137141
populated = populate(wordlist)
142+
with open(f'{arjun_dir}/db/special.json', 'r') as f:
143+
populated.update(json.load(f))
138144
param_groups = slicer(populated, int(len(wordlist)/mem.var['chunks']))
139145
prev_chunk_count = len(param_groups)
140146
last_params = []
@@ -157,7 +163,8 @@ def initialize(request, wordlist, single_url=False):
157163
if reason:
158164
name = list(param.keys())[0]
159165
confirmed_params.append(name)
160-
print('%s parameter detected: %s, based on: %s' % (res, name, reason))
166+
if single_url:
167+
print('%s parameter detected: %s, based on: %s' % (res, name, reason))
161168
return confirmed_params
162169

163170

@@ -171,14 +178,15 @@ def main():
171178
# in case of a single target
172179
mem.var['kill'] = False
173180
url = request['url']
174-
these_params = initialize(request, wordlist)
181+
these_params = initialize(request, wordlist, single_url=True)
175182
if these_params == 'skipped':
176183
print('%s Skipped %s due to errors' % (bad, request['url']))
177184
elif these_params:
178185
final_result[url] = {}
179186
final_result[url]['params'] = these_params
180187
final_result[url]['method'] = request['method']
181188
final_result[url]['headers'] = request['headers']
189+
print('%s Parameters found: %s' % (good, ', '.join(final_result[url]['params'])))
182190
exporter(final_result)
183191
else:
184192
print('%s No parameters were discovered.' % info)

arjun/core/anomaly.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def compare(response, factors, params):
8989
return ('param name reflection', params)
9090
if factors['value_missing']:
9191
for value in params.values():
92-
if type(value) != str:
92+
if type(value) != str or len(value) != 6:
9393
continue
9494
if value in response.text and re.search(r'[\'"\s]%s[\'"\s]' % value, response.text):
9595
return ('param value reflection', params)

arjun/core/exporter.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ def burp_export(result):
1818
exports results to Burp Suite by sending request to Burp proxy
1919
"""
2020
proxies = {
21-
'http': 'http://' + mem.var['burp_port'],
22-
'https': 'https://' + mem.var['burp_port']
21+
'http': 'http://127.0.0.1:' + mem.var['burp_port'],
22+
'https': 'https://127.0.0.1:' + mem.var['burp_port']
2323
}
2424
for url, data in result.items():
2525
if data['method'] == 'GET':

arjun/core/requester.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def requester(request, payload={}):
1818
if len(request.get('include', '')) != 0:
1919
payload.update(request['include'])
2020
if mem.var['stable']:
21-
mem.var['delay'] = random.choice(range(6, 12))
21+
mem.var['delay'] = random.choice(range(3, 10))
2222
time.sleep(mem.var['delay'])
2323
url = request['url']
2424
if mem.var['kill']:

arjun/db/special.json

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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/heuristic.py

+18-9
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
11
import re
22

3+
from arjun.core.colors import info
4+
import arjun.core.config as mem
35
from arjun.core.utils import extract_js
46

5-
re_not_junk = re.compile(r'^[A-Za-z0-9_]+$')
6-
7-
8-
def is_not_junk(param):
9-
return (re_not_junk.match(param) is not None)
10-
117
# 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_]+$')
1210
re_input_names = re.compile(r'''(?i)<input.+?name=["']?([^"'\s>]+)''')
1311
re_input_ids = re.compile(r'''(?i)<input.+?id=["']?([^"'\s>]+)''')
1412
re_empty_vars = re.compile(r'''(?:[;\n]|\bvar|\blet)(\w+)\s*=\s*(?:['"`]{1,2}|true|false|null)''')
1513
re_map_keys = re.compile(r'''['"](\w+?)['"]\s*:\s*['"`]''')
1614

15+
def is_not_junk(param):
16+
return (re_not_junk.match(param) is not None)
1717

18-
def heuristic(response, wordlist):
18+
def heuristic(raw_response, wordlist):
19+
words_exist = False
1920
potential_params = []
2021

22+
headers, response = raw_response.headers, raw_response.text
23+
if headers.get('content-type', '').startswith(('application/json', 'text/plain')):
24+
if len(response) < 200:
25+
if ('required' or 'missing' or 'not found' or 'requires') in response.lower() and ('param' or 'parameter' or 'field') in response.lower():
26+
if not mem.var['quiet']:
27+
print('%s The endpoint seems to require certain parameters to function. Check the repsonse and use the --include option appropriately for better results.' % info)
28+
words_exist = True
29+
potential_params = re_words.findall(response)
2130
# Parse Inputs
2231
input_names = re_input_names.findall(response)
2332
potential_params += input_names
@@ -34,7 +43,7 @@ def heuristic(response, wordlist):
3443
potential_params += map_keys
3544

3645
if len(potential_params) == 0:
37-
return []
46+
return [], words_exist
3847

3948
found = set()
4049
for word in potential_params:
@@ -45,4 +54,4 @@ def heuristic(response, wordlist):
4554
wordlist.remove(word)
4655
wordlist.insert(0, word)
4756

48-
return list(found)
57+
return list(found), words_exist

0 commit comments

Comments
 (0)