Skip to content

Commit

Permalink
Arjun
Browse files Browse the repository at this point in the history
  • Loading branch information
TheKingOfDuck authored Oct 22, 2019
1 parent 92ee4b1 commit a04ade7
Show file tree
Hide file tree
Showing 11 changed files with 27,110 additions and 0 deletions.
25 changes: 25 additions & 0 deletions paramDict/Arjun/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#### 1.5
- Ignore dynamic content
- Detect int-only parameters
- Include URL in json output
- Track each reflection separately
- Improved error handling

#### 1.4
- Added `JSON` support
- Fixed a major bug in detection logic
- `-o` option to save result to a file
- `--urls` option to scan list of URLs
- Ability to supply HTTP headers from CLI

#### 1.3
- improved logic
- detection by plain-text content matching
- `--include` switch to include persistent data
- fixed a bug that caused user supplied HTTP headers to have no effect

#### 1.2-beta
- Drastic performance improvement (x50 faster)

#### 1.1
Initial stable release
674 changes: 674 additions & 0 deletions paramDict/Arjun/LICENSE

Large diffs are not rendered by default.

60 changes: 60 additions & 0 deletions paramDict/Arjun/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@

<h1 align="center">
<br>
<a href="https://github.com/s0md3v/Arjun"><img src="https://image.ibb.co/c618nq/arjun.png" alt="Arjun"></a>
<br>
Arjun
<br>
</h1>

<h4 align="center">HTTP Parameter Discovery Suite</h4>

<p align="center">
<a href="https://github.com/s0md3v/Arjun/releases">
<img src="https://img.shields.io/github/release/s0md3v/Arjun.svg">
</a>
<a href="https://github.com/s0md3v/Arjun/issues?q=is%3Aissue+is%3Aclosed">
<img src="https://img.shields.io/github/issues-closed-raw/s0md3v/Arjun.svg">
</a>
<img src="https://img.shields.io/badge/god%20level-shit-green.svg">
</p>

![demo](https://i.ibb.co/0V6ymPy/Screenshot-2019-04-12-18-17-49.png)

### Introduction
Web applications use parameters (or queries) to accept user input, take the following example into consideration

`http://api.example.com/v1/userinfo?id=751634589`

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?\
This is what Arjun does, it finds valid HTTP parameters with a huge default dictionary of 25,980 parameter names.

The best part? It takes less than 30 seconds to go through this huge list while making just 30-35 requests to the target.\
Want to know how Arjun does that? [Here's how](https://github.com/s0md3v/Arjun/wiki/How-Arjun-works%3F).

### Features
- Multi-threading
- Thorough detection
- `GET/POST/JSON` methods supported
- A typical scan takes 30 seconds
- Regex powered heuristic scanning
- Huge list of 25,980 parameter names
- Makes just 30-35 requests to the target

> **Note:** Arjun doesn't work with python < 3.4
#### How to use Arjun?

A detailed usage guide is available on [Usage](https://github.com/s0md3v/Arjun/wiki/Usage) section of the Wiki.\
An index of options is given below:

- [Scanning a single URL](https://github.com/s0md3v/Arjun/wiki/Usage#scanning-a-single-url)
- [Scanning multiple URLs](https://github.com/s0md3v/Arjun/wiki/Usage#scanning-multiple-urls)
- [Choosing number of threads](https://github.com/s0md3v/Arjun/wiki/Usage#multi-threading)
- [Delay between requests](https://github.com/s0md3v/Arjun/wiki/Usage#delay-between-requests)
- [Including presistent data](https://github.com/s0md3v/Arjun/wiki/Usage#including-persistent-data)
- [Saving output to a file](https://github.com/s0md3v/Arjun/wiki/Usage#saving-output-to-a-file)
- [Adding custom HTTP headers](https://github.com/s0md3v/Arjun/wiki/Usage#adding-http-headers)

##### Credits
The parameter names are taken from [@SecLists](https://github.com/danielmiessler/SecLists).
268 changes: 268 additions & 0 deletions paramDict/Arjun/arjun.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
#!/usr/bin/env python3

from __future__ import print_function

from core.colors import red, green, white, end, info, bad, good, run

print('''%s _
/_| _ '
( |/ /(//) %sv1.5%s
_/ %s
''' % (green, white, green, end))

try:
import concurrent.futures
except ImportError:
print ('%s Please use Python > 3.2 to run Arjun.' % bad)
quit()

import re
import sys
import json
import requests
import argparse

from urllib.parse import unquote

import core.config
from core.prompt import prompt
from core.requester import requester
from core.utils import e, d, stabilize, randomString, slicer, joiner, unityExtracter, getParams, flattenParams, removeTags, extractHeaders, log

parser = argparse.ArgumentParser() #defines the parser
#Arguements that can be supplied
parser.add_argument('-u', help='target url', dest='url')
parser.add_argument('-f', help='wordlist path', dest='wordlist')
parser.add_argument('-d', help='request delay', dest='delay', type=int)
parser.add_argument('-t', help='number of threads', dest='threads', type=int)
parser.add_argument('-o', help='path for the output file', dest='output_file')
parser.add_argument('--urls', help='file containing urls', dest='url_file')
parser.add_argument('--get', help='use get method', dest='GET', action='store_true')
parser.add_argument('--post', help='use post method', dest='POST', action='store_true')
parser.add_argument('--include', help='include this data in every request', dest='include')
parser.add_argument('--headers', help='add headers', dest='headers', nargs='?', const=True)
parser.add_argument('--json', help='treat post data as json', dest='jsonData', action='store_true')
args = parser.parse_args() #arguments to be parsed

url = args.url
jsonData = args.jsonData
headers = args.headers
delay = args.delay or 0
url_file = args.url_file
include = args.include or {}
threadCount = args.threads or 2
wordlist = args.wordlist or './db/params.txt'

core.config.globalVariables = vars(args)

if type(headers) == bool:
headers = extractHeaders(prompt())
elif type(headers) == str:
headers = extractHeaders(headers)
else:
headers = {}

if jsonData:
headers['Content-type'] = 'application/json'

if args.GET:
GET = True
else:
GET = False

include = getParams(include)

paramList = []
try:
with open(wordlist, 'r') as file:
for line in file:
paramList.append(line.strip('\n'))
except FileNotFoundError:
log('%s The specified file for parameters doesn\'t exist' % bad)
quit()

urls = []

if url_file:
try:
with open(url_file, 'r') as file:
for line in file:
urls.append(line.strip('\n'))
except FileNotFoundError:
log('%s The specified file for URLs doesn\'t exist' % bad)
quit()

if not url and not url_file:
log('%s No URL specified.' % bad)
quit()

def heuristic(response, paramList):
done = []
forms = re.findall(r'(?i)(?s)<form.*?</form.*?>', response)
for form in forms:
method = re.search(r'(?i)method=[\'"](.*?)[\'"]', form)
inputs = re.findall(r'(?i)(?s)<input.*?>', response)
for inp in inputs:
inpName = re.search(r'(?i)name=[\'"](.*?)[\'"]', inp)
if inpName:
inpType = re.search(r'(?i)type=[\'"](.*?)[\'"]', inp)
inpValue = re.search(r'(?i)value=[\'"](.*?)[\'"]', inp)
inpName = d(e(inpName.group(1)))
if inpName not in done:
if inpName in paramList:
paramList.remove(inpName)
done.append(inpName)
paramList.insert(0, inpName)
log('%s Heuristic found a potential parameter: %s%s%s' % (good, green, inpName, end))
log('%s Prioritizing it' % good)

def quickBruter(params, originalResponse, originalCode, reflections, factors, include, delay, headers, url, GET):
joined = joiner(params, include)
newResponse = requester(url, joined, headers, GET, delay)
if newResponse.status_code == 429:
print ('%s Target has rate limiting in place, please use -t 2 -d 5.' % bad)
raise ConnectionError
if newResponse.status_code != originalCode:
return params
elif factors['sameHTML'] and len(newResponse.text) != (len(originalResponse)):
return params
elif factors['samePlainText'] and len(removeTags(originalResponse)) != len(removeTags(newResponse.text)):
return params
elif True:
for param, value in joined.items():
if param not in include and newResponse.text.count(value) != reflections:
return params
else:
return False

def bruter(param, originalResponse, originalCode, factors, include, reflections, delay, headers, url, GET):
fuzz = randomString(6)
data = {param : fuzz}
data.update(include)
response = requester(url, data, headers, GET, delay)
newReflections = response.text.count(fuzz)
reason = False
if response.status_code != originalCode:
reason = 'Different response code'
elif reflections != newReflections:
reason = 'Different number of reflections'
elif factors['sameHTML'] and len(response.text) != (len(originalResponse)):
reason = 'Different content length'
elif factors['samePlainText'] and len(removeTags(response.text)) != (len(removeTags(originalResponse))):
reason = 'Different plain-text content length'
if reason:
return {param : reason}
else:
return None

def narrower(oldParamList, url, include, headers, GET, delay, originalResponse, originalCode, reflections, factors, threadCount):
newParamList = []
threadpool = concurrent.futures.ThreadPoolExecutor(max_workers=threadCount)
futures = (threadpool.submit(quickBruter, part, originalResponse, originalCode, reflections, factors, include, delay, headers, url, GET) for part in oldParamList)
for i, result in enumerate(concurrent.futures.as_completed(futures)):
if result.result():
newParamList.extend(slicer(result.result()))
log('%s Processing: %i/%-6i' % (info, i + 1, len(oldParamList)), mode='run')
return newParamList

def initialize(url, include, headers, GET, delay, paramList, threadCount):
url = stabilize(url)

log('%s Analysing the content of the webpage' % run)
firstResponse = requester(url, include, headers, GET, delay)

log('%s Analysing behaviour for a non-existent parameter' % run)

originalFuzz = randomString(6)
data = {originalFuzz : originalFuzz[::-1]}
data.update(include)
response = requester(url, data, headers, GET, delay)
reflections = response.text.count(originalFuzz[::-1])
log('%s Reflections: %s%i%s' % (info, green, reflections, end))

originalResponse = response.text
originalCode = response.status_code
log('%s Response Code: %s%i%s' % (info, green, originalCode, end))

newLength = len(response.text)
plainText = removeTags(originalResponse)
plainTextLength = len(plainText)
log('%s Content Length: %s%i%s' % (info, green, newLength, end))
log('%s Plain-text Length: %s%i%s' % (info, green, plainTextLength, end))

factors = {'sameHTML': False, 'samePlainText': False}
if len(firstResponse.text) == len(originalResponse):
factors['sameHTML'] = True
elif len(removeTags(firstResponse.text)) == len(plainText):
factors['samePlainText'] = True

log('%s Parsing webpage for potential parameters' % run)
heuristic(firstResponse.text, paramList)

fuzz = randomString(8)
data = {fuzz : fuzz[::-1]}
data.update(include)

log('%s Performing heuristic level checks' % run)

toBeChecked = slicer(paramList, 50)
foundParams = []
while True:
try:
toBeChecked = narrower(toBeChecked, url, include, headers, GET, delay, originalResponse, originalCode, reflections, factors, threadCount)
toBeChecked = unityExtracter(toBeChecked, foundParams)
if not toBeChecked:
break
except:
raise ConnectionError

if foundParams:
log('%s Heuristic found %i potential parameters.' % (info, len(foundParams)))
paramList = foundParams

currentResult = []
returnResult = []

threadpool = concurrent.futures.ThreadPoolExecutor(max_workers=threadCount)
futures = (threadpool.submit(bruter, param, originalResponse, originalCode, factors, include, reflections, delay, headers, url, GET) for param in foundParams)
for i, result in enumerate(concurrent.futures.as_completed(futures)):
if result.result():
currentResult.append(result.result())
log('%s Progress: %i/%i' % (info, i + 1, len(paramList)), mode='run')

log('%s Scan Completed ' % info)

for each in currentResult:
for param, reason in each.items():
log('%s Valid parameter found: %s%s%s' % (good, green, param, end))
log('%s Reason: %s' % (info, reason))
returnResult.append({"param": param, "reason": reason})
if not returnResult:
log('%s Unable to verify existence of parameters detected by heuristic' % bad)
return returnResult

finalResult = {}
if url:
finalResult[url] = []
try:
finalResult[url] = initialize(url, include, headers, GET, delay, paramList, threadCount)
except ConnectionError:
print ('%s Target is refusing connections. Consider using -d 5 -t 1.' % bad)
quit()
elif urls:
for url in urls:
finalResult[url] = []
print('%s Scanning: %s' % (run, url))
try:
finalResult[url] = initialize(url, include, headers, GET, delay, list(paramList), threadCount)
if finalResult[url]:
print('%s Parameters found: %s' % (good, ', '.join([each['param'] for each in finalResult[url]])))
except ConnectionError:
print ('%s Target is refusing connections. Consider using -d 5 -t 1.' % bad)
pass

# Finally, export to json
if args.output_file and finalResult:
log('%s Saving output to JSON file in %s' % (info, args.output_file))
with open(str(args.output_file), 'w+') as json_output:
json.dump(finalResult, json_output, sort_keys=True, indent=4)
1 change: 1 addition & 0 deletions paramDict/Arjun/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

20 changes: 20 additions & 0 deletions paramDict/Arjun/core/colors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import sys

colors = True # Output should be colored
machine = sys.platform # Detecting the os of current system
if machine.lower().startswith(('os', 'win', 'darwin', 'ios')):
colors = False # Colors shouldn't be displayed in mac & windows
if not colors:
end = red = white = green = yellow = run = bad = good = info = que = ''
else:
white = '\033[97m'
green = '\033[92m'
red = '\033[91m'
yellow = '\033[93m'
end = '\033[0m'
back = '\033[7;91m'
info = '\033[93m[!]\033[0m'
que = '\033[94m[?]\033[0m'
bad = '\033[91m[-]\033[0m'
good = '\033[92m[+]\033[0m'
run = '\033[97m[~]\033[0m'
1 change: 1 addition & 0 deletions paramDict/Arjun/core/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
globalVariables = {}
Loading

0 comments on commit a04ade7

Please sign in to comment.