Skip to content

Commit dab63ee

Browse files
authored
Stable release v2.2.2 (Fixes #122)
2 parents 25abb08 + 5d468cf commit dab63ee

File tree

5 files changed

+58
-140
lines changed

5 files changed

+58
-140
lines changed

.travis.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ language: python
22
os:
33
- linux
44
python:
5-
- 2.7
65
- 3.6
76
install:
87
- pip install -r requirements.txt
@@ -14,4 +13,4 @@ before_script:
1413
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
1514
script:
1615
- python photon.py -u "https://stackoverflow.com" -l 1 -d 1 -t 100 --regex "\d{10}" --dns --output="d3v"
17-
- python photon.py -u "https://stackoverflow.com" -l 1 -t 10 --seeds="https://stackoverflow.com/jobs" --only-urls --export=json --ninja
16+
- python photon.py -u "https://rocket.chat" -l 1 -t 10 --seeds="https://stackoverflow.com/jobs" --only-urls --export=json --wayback

core/flash.py

+7-45
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,17 @@
11
from __future__ import print_function
2-
import sys
3-
import threading
2+
import concurrent.futures
43

54
from core.colors import info
65

7-
try:
8-
import concurrent.futures
9-
except ImportError:
10-
pass
11-
12-
13-
def threader(function, *urls):
14-
"""Start multiple threads for a function."""
15-
threads = []
16-
# Because URLs is a tuple
17-
urls = urls[0]
18-
# Iterating over URLs
19-
for url in urls:
20-
task = threading.Thread(target=function, args=(url,))
21-
threads.append(task)
22-
# Start threads
23-
for thread in threads:
24-
thread.start()
25-
# Wait for all threads to complete their work
26-
for thread in threads:
27-
thread.join()
28-
# Delete threads
29-
del threads[:]
30-
31-
326
def flash(function, links, thread_count):
337
"""Process the URLs and uses a threadpool to execute a function."""
348
# Convert links (set) to list
359
links = list(links)
36-
if sys.version_info < (3, 2):
37-
for begin in range(0, len(links), thread_count): # Range with step
38-
end = begin + thread_count
39-
splitted = links[begin:end]
40-
threader(function, splitted)
41-
progress = end
42-
if progress > len(links): # Fix if overflow
43-
progress = len(links)
44-
print('\r%s Progress: %i/%i' % (info, progress, len(links)),
45-
end='\r')
46-
sys.stdout.flush()
47-
else:
48-
threadpool = concurrent.futures.ThreadPoolExecutor(
10+
threadpool = concurrent.futures.ThreadPoolExecutor(
4911
max_workers=thread_count)
50-
futures = (threadpool.submit(function, link) for link in links)
51-
for i, _ in enumerate(concurrent.futures.as_completed(futures)):
52-
if i + 1 == len(links) or (i + 1) % thread_count == 0:
53-
print('%s Progress: %i/%i' % (info, i + 1, len(links)),
54-
end='\r')
12+
futures = (threadpool.submit(function, link) for link in links)
13+
for i, _ in enumerate(concurrent.futures.as_completed(futures)):
14+
if i + 1 == len(links) or (i + 1) % thread_count == 0:
15+
print('%s Progress: %i/%i' % (info, i + 1, len(links)),
16+
end='\r')
5517
print('')

core/requester.py

+2-49
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ def requester(
1616
headers=None,
1717
timeout=10,
1818
host=None,
19-
ninja=False,
2019
user_agents=None,
2120
failed=None,
2221
processed=None
@@ -32,7 +31,7 @@ def requester(
3231
# Pause/sleep the program for specified time
3332
time.sleep(delay)
3433

35-
def normal(url):
34+
def make_request(url):
3635
"""Default request"""
3736
final_headers = headers or {
3837
'Host': host,
@@ -66,50 +65,4 @@ def normal(url):
6665
response.close()
6766
return 'dummy'
6867

69-
def facebook(url):
70-
"""Interact with the developer.facebook.com API."""
71-
return requests.get(
72-
'https://developers.facebook.com/tools/debug/echo/?q=' + url,
73-
verify=False
74-
).text
75-
76-
def pixlr(url):
77-
"""Interact with the pixlr.com API."""
78-
if url == main_url:
79-
# Because pixlr throws error if http://example.com is used
80-
url = main_url + '/'
81-
return requests.get(
82-
'https://pixlr.com/proxy/?url=' + url,
83-
headers={'Accept-Encoding': 'gzip'},
84-
verify=False
85-
).text
86-
87-
def code_beautify(url):
88-
"""Interact with the codebeautify.org API."""
89-
headers = {
90-
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0',
91-
'Accept': 'text/plain, */*; q=0.01',
92-
'Accept-Encoding': 'gzip',
93-
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
94-
'Origin': 'https://codebeautify.org',
95-
'Connection': 'close',
96-
}
97-
return requests.post(
98-
'https://codebeautify.com/URLService',
99-
headers=headers,
100-
data='path=' + url,
101-
verify=False
102-
).text
103-
104-
def photopea(url):
105-
"""Interact with the www.photopea.com API."""
106-
return requests.get(
107-
'https://www.photopea.com/mirror.php?url=' + url, verify=False).text
108-
109-
if ninja: # If the ninja mode is enabled
110-
# Select a random request function i.e. random API
111-
response = random.choice(
112-
[photopea, normal, facebook, pixlr, code_beautify])(url)
113-
return response or 'dummy'
114-
else:
115-
return normal(url)
68+
return make_request(url)

core/utils.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ def is_link(url, processed, files):
4141
is_file = url.endswith(BAD_TYPES)
4242
if is_file:
4343
files.add(url)
44-
return is_file
44+
return False
45+
return True
4546
return False
4647

4748

@@ -78,7 +79,7 @@ def writer(datasets, dataset_names, output_dir):
7879
filepath = output_dir + '/' + dataset_name + '.txt'
7980
with open(filepath, 'w+') as out_file:
8081
joined = '\n'.join(dataset)
81-
out_file.write(str(joined.encode('utf-8')))
82+
out_file.write(str(joined.encode('utf-8').decode('utf-8')))
8283
out_file.write('\n')
8384

8485
def timer(diff, processed):

photon.py

+45-42
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,28 @@
66
import argparse
77
import os
88
import re
9+
import requests
910
import sys
1011
import time
1112
import warnings
1213

13-
import requests
14-
1514
from core.colors import good, info, run, green, red, white, end
15+
16+
# Just a fancy ass banner
17+
print('''%s ____ __ __
18+
/ %s__%s \/ /_ ____ / /_____ ____
19+
/ %s/_/%s / __ \/ %s__%s \/ __/ %s__%s \/ __ \\
20+
/ ____/ / / / %s/_/%s / /_/ %s/_/%s / / / /
21+
/_/ /_/ /_/\____/\__/\____/_/ /_/ %sv1.2.2%s\n''' %
22+
(red, white, red, white, red, white, red, white, red, white, red, white,
23+
red, white, end))
24+
25+
try:
26+
from urllib.parse import urlparse # For Python 3
27+
except ImportError:
28+
print ('%s Photon runs only on Python 3.2 and above.' % info)
29+
quit()
30+
1631
import core.config
1732
from core.config import INTELS
1833
from core.flash import flash
@@ -23,28 +38,6 @@
2338
from core.utils import top_level, extract_headers, verb, is_link, entropy, regxy, remove_regex, timer, writer
2439
from core.zap import zap
2540

26-
try:
27-
from urllib.parse import urlparse # For Python 3
28-
python2, python3 = False, True
29-
except ImportError:
30-
from urlparse import urlparse # For Python 2
31-
python2, python3 = True, False
32-
33-
34-
try:
35-
input = raw_input
36-
except NameError:
37-
pass
38-
39-
40-
# Just a fancy ass banner
41-
print('''%s ____ __ __
42-
/ %s__%s \/ /_ ____ / /_____ ____
43-
/ %s/_/%s / __ \/ %s__%s \/ __/ %s__%s \/ __ \\
44-
/ ____/ / / / %s/_/%s / /_/ %s/_/%s / / / /
45-
/_/ /_/ /_/\____/\__/\____/_/ /_/ %sv1.2.1%s\n''' %
46-
(red, white, red, white, red, white, red, white, red, white, red, white,
47-
red, white, end))
4841

4942
# Disable SSL related warnings
5043
warnings.filterwarnings('ignore')
@@ -82,8 +75,6 @@
8275
action='store_true')
8376
parser.add_argument('--dns', help='enumerate subdomains and DNS data',
8477
dest='dns', action='store_true')
85-
parser.add_argument('--ninja', help='ninja mode', dest='ninja',
86-
action='store_true')
8778
parser.add_argument('--keys', help='find secret keys', dest='api',
8879
action='store_true')
8980
parser.add_argument('--update', help='update photon', dest='update',
@@ -118,7 +109,6 @@
118109
timeout = args.timeout or 6 # HTTP request timeout
119110
cook = args.cook or None # Cookie
120111
api = bool(args.api) # Extract high entropy strings i.e. API keys and stuff
121-
ninja = bool(args.ninja) # Ninja mode toggle
122112
crawl_level = args.level or 2 # Crawling level
123113
thread_count = args.threads or 2 # Number of threads
124114
only_urls = bool(args.only_urls) # Only URLs mode is off by default
@@ -135,12 +125,11 @@
135125
# URLs that have get params in them e.g. example.com/page.php?id=2
136126
fuzzable = set()
137127
endpoints = set() # URLs found from javascript files
138-
processed = set() # URLs that have been crawled
128+
processed = set(['dummy']) # URLs that have been crawled
139129
# URLs that belong to the target i.e. in-scope
140130
internal = set(args.seeds)
141131

142132
everything = []
143-
bad_intel = set() # Unclean intel urls
144133
bad_scripts = set() # Unclean javascript file urls
145134

146135
core.config.verbose = verbose
@@ -180,13 +169,13 @@
180169

181170
supress_regex = False
182171

183-
def intel_extractor(response):
172+
def intel_extractor(url, response):
184173
"""Extract intel from the response body."""
185174
matches = re.findall(r'([\w\.-]+s[\w\.-]+\.amazonaws\.com)|([\w\.-]+@[\w\.-]+\.[\.\w]+)', response)
186175
if matches:
187176
for match in matches:
188177
verb('Intel', match)
189-
bad_intel.add(match)
178+
intel.add(url + ':' + ''.join(list(match)))
190179

191180

192181
def js_extractor(response):
@@ -198,12 +187,22 @@ def js_extractor(response):
198187
verb('JS file', match)
199188
bad_scripts.add(match)
200189

190+
def remove_file(url):
191+
if url.count('/') > 2:
192+
replacable = re.search(r'/[^/]*?$', url).group()
193+
if replacable != '/':
194+
return url.replace(replacable, '')
195+
else:
196+
return url
197+
else:
198+
return url
199+
201200
def extractor(url):
202201
"""Extract details from the response body."""
203-
response = requester(url, main_url, delay, cook, headers, timeout, host, ninja, user_agents, failed, processed)
202+
response = requester(url, main_url, delay, cook, headers, timeout, host, user_agents, failed, processed)
204203
if clone:
205204
mirror(url, response)
206-
matches = re.findall(r'<[aA].*(href|HREF)=([^\s>]+)', response)
205+
matches = re.findall(r'<[aA][^>]*?(href|HREF)=([^\s>]+)', response)
207206
for link in matches:
208207
# Remove everything after a "#" to deal with in-page anchors
209208
link = link[1].replace('\'', '').replace('"', '').split('#')[0]
@@ -219,19 +218,25 @@ def extractor(url):
219218
elif link[:2] == '//':
220219
if link.split('/')[2].startswith(host):
221220
verb('Internal page', link)
222-
internal.add(schema + link)
221+
internal.add(schema + '://' + link)
223222
else:
224223
verb('External page', link)
225224
external.add(link)
226225
elif link[:1] == '/':
227226
verb('Internal page', link)
228-
internal.add(main_url + link)
227+
internal.add(remove_file(url) + link)
229228
else:
230229
verb('Internal page', link)
231-
internal.add(main_url + '/' + link)
230+
usable_url = remove_file(url)
231+
if usable_url.endswith('/'):
232+
internal.add(usable_url + link)
233+
elif link.startswith('/'):
234+
internal.add(usable_url + link)
235+
else:
236+
internal.add(usable_url + '/' + link)
232237

233238
if not only_urls:
234-
intel_extractor(response)
239+
intel_extractor(url, response)
235240
js_extractor(response)
236241
if args.regex and not supress_regex:
237242
regxy(args.regex, response, supress_regex, custom)
@@ -245,7 +250,7 @@ def extractor(url):
245250

246251
def jscanner(url):
247252
"""Extract endpoints from JavaScript code."""
248-
response = requester(url, main_url, delay, cook, headers, timeout, host, ninja, user_agents, failed, processed)
253+
response = requester(url, main_url, delay, cook, headers, timeout, host, user_agents, failed, processed)
249254
# Extract URLs/endpoints
250255
matches = re.findall(r'[\'"](/.*?)[\'"]|[\'"](http.*?)[\'"]', response)
251256
# Iterate over the matches, match is a tuple
@@ -301,10 +306,8 @@ def jscanner(url):
301306
if '=' in url:
302307
fuzzable.add(url)
303308

304-
for match in bad_intel:
305-
for x in match: # Because "match" is a tuple
306-
if x != '': # If the value isn't empty
307-
intel.add(x)
309+
for match in intel:
310+
intel.add(match)
308311
for url in external:
309312
try:
310313
if top_level(url, fix_protocol=True) in INTELS:

0 commit comments

Comments
 (0)