Skip to content
This repository was archived by the owner on Sep 16, 2023. It is now read-only.

Commit f8bc8b7

Browse files
authored
Merge pull request #4 from shogunlab/fuzzy_detection
Fuzzy detection of partial XSS reflections enabled, new detect_xss() method, docstrings
2 parents 7ec0717 + 8c78e2f commit f8bc8b7

4 files changed

Lines changed: 160 additions & 54 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ ghostdriver.log
77
/payloads/*
88
/logs/*
99
/screenshots/*
10+
11+
.vscode

README.md

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ Shuriken works with [Python](http://www.python.org/download/) **2.7.x** on any p
1717
## Features
1818
- Easily specify where in a URL the payload should be injected with the "{xss}" string.
1919
- Quickly change payload lists.
20-
- Take screenshots of successful XSS hits.
20+
- Take screenshots of successful XSS payloads.
2121
- Save logs of reflected XSS payloads.
22+
- Use fuzzy detection to log partial XSS reflections.
2223

2324
## Usage
2425
To get a list of options and switches, enter:
@@ -29,34 +30,60 @@ To test a list of payloads against a target URL, specify where the payloads will
2930

3031
`python shuriken_xss.py -u "http://example.com/target.php?name={xss}" -p "xss-payload-list.txt"`
3132

32-
If you would like to screenshot and save all reflected XSS payloads, use the *--screen* flag with a name for the screenshot images and enter:
33+
### Taking screenshots
3334

34-
`python shuriken_xss.py -u "http://example.com/target.php?name={xss}" -p "xss-payload-list.txt" --screen ExampleTarget`
35+
If you would like to screenshot and save all reflected XSS payloads, use the *-s* or *--screen* flag with a name for the screenshot images and enter:
36+
37+
`python shuriken_xss.py -s ExampleTarget -u "http://example.com/target.php?name={xss}" -p "xss-payload-list.txt"`
3538

3639
To wait a specific amount of time in between requests, use the *-t* flag with the amount of time to wait in seconds and enter:
3740

38-
`python shuriken_xss.py -u "http://example.com/target.php?name={xss}" -p "xss-payload-list.txt" -t 1.5`
41+
`python shuriken_xss.py -t 1.5 -u "http://example.com/target.php?name={xss}" -p "xss-payload-list.txt"`
42+
43+
### Fuzzy XSS payload detection
44+
45+
To enable partial or fuzzy detection of XSS payloads in HTML source code, use the *-f* or *--fuzzy* flag with the level of detection you want to log. For example, the following command will only log XSS payload reflections that have a 75% matching score or above in the HTML source code returned:
46+
47+
`python shuriken_xss.py -f 75 -u "http://example.com/target.php?name={xss}" -p "xss-payload-list.txt"`
48+
49+
The default matching score supplied is 50% and will be applied when a flag with no number is given (e.g. -f or --fuzzy):
50+
51+
`python shuriken_xss.py -f -u "http://example.com/target.php?name={xss}" -p "xss-payload-list.txt"`
52+
53+
Partial detection is applied through the use of SeatGeek's [FuzzyWuzzy](https://github.com/seatgeek/fuzzywuzzy) Python library `token_set_ratio()` method and additional information regarding this library can be found [here](http://chairnerd.seatgeek.com/fuzzywuzzy-fuzzy-string-matching-in-python/).
54+
55+
Partial XSS reflections will be logged in a separate text file ending with "_partials.txt".
56+
57+
### Misc. usage and performance notes
3958

4059
**You must specify a payload and URL**, if you don't then you'll get an error. For an example payload to test with, check out this list of [common XSS payloads](https://github.com/foospidy/payloads/blob/master/owasp/fuzzing_code_database/xss/common.txt).
4160

61+
You also must have PhantomJS installed and configured in order for the tool to run in its default mode. See the next section for more details on this.
62+
63+
**There may be a noticeable slowdown of the tool when it is being used in a virtual machine such as VirtualBox.** For best performance, use Shuriken on a native machine. I am currently looking to address this virtual machine slowdown in a future update.
64+
4265
## Third party libraries and dependencies
4366
This tool depends on the proper configuration and installation of the following:
4467
- [Python 2.7.x](https://www.python.org/downloads/) - Python 2 is needed to run the tool.
4568
- [Splinter](https://splinter.readthedocs.io/en/latest/install.html) - Python library allowing use of a headless web browser for testing.
4669
- [PhantomJS](http://phantomjs.org/download.html) - Headless WebKit browser used by Splinter for testing.
4770
- [Selenium 2.0](http://www.seleniumhq.org/docs/03_webdriver.jsp) - WebDriver required by PhantomJS browser.
71+
- [FuzzyWuzzy](https://github.com/seatgeek/fuzzywuzzy) - Partial XSS logging using fuzzy detection methods.
72+
- [python-Levenshtein](https://pypi.python.org/pypi/python-Levenshtein/0.12.0) - Python extension for computing string edit distances and similarities. Allows faster fuzzy detection from the FuzzyWuzzy library.
4873

4974
Python dependencies can be installed using pip: `pip install -r requirements.txt`. Use your platform-specific mechanism to install PhatomJS (e.g. `brew` on OSX, `apt-get` on Debian or Ubuntu, etc).
5075

5176
If you would prefer that this tool ***use a different browser for testing***, you can read the [Splinter docs](https://splinter.readthedocs.io/en/latest/#drivers) and insert your preferred browser in the "inject_payload" method where it says `browser = Browser("phantomjs")`. Leaving it blank as `browser = Browser()` will default to Firefox.
5277

5378
## Screenshots
5479
**Basic usage**
55-
![screen_1](https://i.imgur.com/91dGSfS.png "Shuriken Screenshot #1")
80+
![screen_1](https://i.imgur.com/LzPpTsh.png "Shuriken Screenshot #1")
5681

82+
**With *-s* or *--screen* option to record screenshots and *-t* option to delay requests by specific amount**
83+
![screen_2](https://i.imgur.com/EjYdAGD.png "Shuriken Screenshot #2")
5784

58-
**With *--screen* option to record screenshots and *-t* option to delay requests by specific amount**
59-
![screen_2](https://i.imgur.com/md9j82N.png "Shuriken Screenshot #2")
85+
**With *-f* or *--fuzzy* option to fuzzy detect and log partial XSS payload reflections**
86+
![screen_3](https://i.imgur.com/9lwcFxl.png "Shuriken Screenshot #3")
6087

6188
## Legal
6289
Shuriken was derived from the excellent XSS command line tool by Faizan Ahmad, called [XssPy](https://github.com/faizann24/XssPy). The Shuriken XSS tool is under an MIT license, you can read it [here](https://github.com/shogunlab/shuriken/blob/master/LICENSE.md).

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
splinter==0.7.5
22
selenium==3.4.3
3+
fuzzywuzzy==0.15.1
4+
python-Levenshtein==0.12.0

shuriken_xss.py

Lines changed: 122 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
#!/usr/bin/env python
1+
# !/usr/bin/env python
2+
"""Set Python environment."""
23
# -*- coding: utf-8 -*-
34

45
import argparse
@@ -9,10 +10,12 @@
910
import time
1011

1112
from splinter import Browser
13+
from fuzzywuzzy import fuzz
1214

1315

1416
class Color:
15-
# Use colors to make command line output prettier
17+
"""Use colors to make command line output prettier."""
18+
1619
RED = '\033[91m'
1720
GREEN = '\033[92m'
1821
YELLOW = '\033[93m'
@@ -21,22 +24,31 @@ class Color:
2124

2225

2326
class Shuriken:
27+
"""Object for testing lists of XSS payloads."""
2428

2529
def __init__(self):
30+
"""Initiate object with default encoding and other required data."""
2631
# Expect some weird characters from fuzz lists, make encoding UTF-8
2732
reload(sys)
2833
sys.setdefaultencoding('utf8')
2934

3035
# All potential XSS findings
3136
self.xss_links = []
3237

38+
# Fuzzy string matching partials list
39+
self.xss_partials = []
40+
41+
# Keep index of screens for log files
42+
self.screen_index = ""
43+
3344
# Get user args and store
3445
self.user_args = self.parse_args()
3546

3647
# PhantomJS browser
3748
self.browser = Browser("phantomjs")
3849

3950
def main(self):
51+
"""Call functions to inject XSS and test for vulnerabilities."""
4052
# Print out a welcome message
4153
print "\n"
4254
print "=" * 34 + Color.YELLOW + "\nWelcome to the" + Color.RED + \
@@ -49,11 +61,16 @@ def main(self):
4961
self.user_args.SCREENSHOT_NAME)
5062
print Color.GREEN + "\n=== Testing complete! ===\n" + Color.END
5163

52-
# If the test found possible XSS vulnerabilities, ask if we should
53-
# log
64+
# If the test found possible XSS vulnerabilities, ask if we should log
5465
if self.xss_links:
5566
print Color.YELLOW + \
56-
"Potential XSS vulnerabilities were detected!" + \
67+
"XSS vulnerabilities were detected!" + \
68+
Color.END
69+
self.log_file(self.xss_links)
70+
# There were partials detected by fuzzy detection
71+
elif self.xss_partials:
72+
print Color.YELLOW + \
73+
"Partial XSS vulnerabilities were detected!" + \
5774
Color.END
5875
self.log_file(self.xss_links)
5976
else:
@@ -64,14 +81,16 @@ def main(self):
6481
print "\n"
6582

6683
def make_sure_path_exists(self, path):
84+
"""Ensure that file path exists before writing."""
6785
try:
6886
os.makedirs(path)
6987
except OSError as exception:
7088
if exception.errno != errno.EEXIST:
7189
raise
7290

73-
def inject_payload(self, payload, link, request_delay, screenshot_target):
74-
# Visit user supplied link with injected payload
91+
def inject_payload(self, payload, link, request_delay,
92+
user_screenshot_name):
93+
"""Inject XSS payload string from user supplied payload list."""
7594
browser = self.browser
7695

7796
# Let user specify where in the URL fuzz values should be injected
@@ -95,31 +114,69 @@ def inject_payload(self, payload, link, request_delay, screenshot_target):
95114
# linked to line nums in log
96115
self.screen_index = str(len(self.xss_links) + 1)
97116

98-
# Check to see if payload was reflected in HTML source
99-
if payload in browser.html:
100-
print Color.GREEN + "\n[+] Potential XSS vulnerability found:" + \
117+
# Check to see if payload was reflected in HTML source,
118+
# if so, take screenshot depending on user flag
119+
self.detect_xss(payload, browser, user_screenshot_name, injected_link)
120+
121+
def detect_xss(self, payload, browser_object, user_screenshot_name,
122+
injected_link):
123+
"""Check the HTML source to determine if XSS payload was reflected."""
124+
# If fuzzy detection chosen, evaluate partial reflection of XSS
125+
# by tokenizing the HTML source and detecting parts of the payload
126+
# and source common to both.
127+
#
128+
# Other methods of scoring include fuzz.ratio(), fuzz.partial_ratio()
129+
# and fuzz.token_sort_ratio()
130+
partial_score = fuzz.token_set_ratio(
131+
payload.lower(), browser_object.html.lower())
132+
# Set the level of detection asked for by the user, e.g. Only detect
133+
# matches with score higher than 50% fuzzy detection
134+
fuzzy_level = self.user_args.FUZZY_DETECTION
135+
136+
if payload.lower() in browser_object.html.lower():
137+
print Color.GREEN + "\n[+] XSS vulnerability found:" + \
101138
Color.END
102-
# If user set the --screen flag to target, capture screen of
139+
140+
# If user set the --screen flag to target, capture screenshot of
103141
# payload
104-
if screenshot_target is not None:
105-
# Check if screenshots directory exists, if not then create it
106-
self.make_sure_path_exists("screenshots")
107-
screenshot_file_name = "screenshots/" + \
108-
screenshot_target + "_" + \
109-
datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + \
110-
"_" + self.screen_index + ".png"
111-
# Save screenshot to directory
112-
browser.driver.save_screenshot(screenshot_file_name)
113-
print Color.YELLOW + "Screenshot saved: " + \
114-
screenshot_file_name + Color.END
142+
if user_screenshot_name is not None:
143+
self.take_screenshot(user_screenshot_name,
144+
browser_object, self.screen_index)
145+
115146
# Add link to list of all positive XSS hits
116147
self.xss_links.append(injected_link)
117-
return Color.BLUE + injected_link + Color.END
148+
print Color.BLUE + injected_link + Color.END
149+
# If user enabled fuzzy detection and partial score was larger than
150+
# fuzz level, add it to partials list and print results
151+
elif fuzzy_level and (partial_score >= fuzzy_level):
152+
print Color.YELLOW + \
153+
"\n[-] Partial XSS vulnerability found:" + Color.END
154+
print Color.BLUE + injected_link + Color.END
155+
self.xss_partials.append(injected_link)
156+
print "Detection score: %s" % partial_score
118157
else:
119-
return Color.YELLOW + "\n[+] Tested, but no XSS found at: \n" + \
120-
Color.RED + injected_link + Color.END
158+
print Color.RED + "\n[+] No XSS detected at: \n" + \
159+
Color.BLUE + injected_link + Color.END
160+
if (fuzzy_level):
161+
print "Detection score: %s" % partial_score
162+
163+
def take_screenshot(self, user_screenshot_name, browser_object,
164+
screen_index):
165+
"""Take a screenshot of the page in the browser object."""
166+
# Check if screenshots directory exists, if not then create it
167+
self.make_sure_path_exists("screenshots")
168+
screenshot_file_name = "screenshots/" + \
169+
user_screenshot_name + "_" + \
170+
datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + \
171+
"_" + screen_index + ".png"
172+
# Save screenshot to directory
173+
browser_object.driver.save_screenshot(screenshot_file_name)
174+
print Color.YELLOW + "Screenshot saved: " + \
175+
screenshot_file_name + Color.END
121176

122-
def test_xss(self, payloads_param, link, request_delay, screenshot_target):
177+
def test_xss(self, payloads_param, link, request_delay,
178+
user_screenshot_name):
179+
"""Load string from payload list and call function to inject it."""
123180
# If the user added time delay, show them what it is set to.
124181
if request_delay is not None:
125182
print Color.YELLOW + "\n[!] Request delay is set to [" + \
@@ -128,34 +185,48 @@ def test_xss(self, payloads_param, link, request_delay, screenshot_target):
128185
# Load the payload file and inject all payloads
129186
# into user supplied URL to test for XSS
130187
payloads = []
131-
with open(payloads_param) as file:
132-
for line in file:
188+
with open(payloads_param) as payload_file:
189+
for line in payload_file:
133190
line = line.strip()
134191
payloads.append(line)
135192
for item in payloads:
136-
print self.inject_payload(item, link, request_delay,
137-
screenshot_target)
193+
self.inject_payload(item, link, request_delay,
194+
user_screenshot_name)
138195

139196
def log_file(self, link_list):
197+
"""Log successful XSS payload reflections to file."""
140198
# Prompt the user to confirm log file, if yes, log XSS hits
141199
log_confirm = raw_input(
142200
"\nWould you like to save these results? [y/n] > ")
143201
if log_confirm == "y":
144202
target_name = raw_input("Please enter the target name > ")
145203
# Check if logs directory exists, if not then create it
146204
self.make_sure_path_exists("logs")
147-
# Save log file to directory
205+
# Set file name
148206
file_name = "logs/" + target_name + "_" + \
149-
datetime.datetime.now().strftime("%Y%m%d-%H%M%S") + \
150-
".txt"
151-
with open(file_name, 'w') as file:
152-
for link in link_list:
153-
file.write(link)
154-
file.write("\n")
155-
# Add metadata about what payload file was used
156-
file.write("\n*** Created from the payload file >>> " +
157-
self.user_args.PAYLOADS_LIST)
158-
file.close()
207+
datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
208+
# Save log file to directory if XSS links is populated
209+
if self.xss_links:
210+
with open(file_name + ".txt", 'w') as link_file:
211+
for link in link_list:
212+
link_file.write(link)
213+
link_file.write("\n")
214+
# Add metadata about what payload file was used
215+
link_file.write(
216+
"\n*** Created from the payload file >>> " +
217+
self.user_args.PAYLOADS_LIST)
218+
link_file.close()
219+
# If the user enabled fuzzy detection, log partial results
220+
if (self.user_args.FUZZY_DETECTION):
221+
with open(file_name + "_partials.txt", 'w') as partial_file:
222+
for link in self.xss_partials:
223+
partial_file.write(link)
224+
partial_file.write("\n")
225+
# Add metadata about what payload file was used
226+
partial_file.write(
227+
"\n*** Created from the payload file >>> " +
228+
self.user_args.PAYLOADS_LIST)
229+
partial_file.close()
159230
print "\nFile successfully saved as: " + \
160231
Color.BLUE + file_name + Color.END
161232
print "\n"
@@ -164,20 +235,24 @@ def log_file(self, link_list):
164235
print "\n"
165236

166237
def parse_args(self):
167-
# Parse arguments from the user sent on command line
238+
"""Parse arguments from the user sent on command line."""
168239
parser = argparse.ArgumentParser()
169240
parser.add_argument(
170241
'-u', action='store', dest='URL',
171-
help='The URL to analyze', required=True)
242+
help='The URL to inject XSS payloads into.', required=True)
172243
parser.add_argument(
173244
'-p', action='store', dest='PAYLOADS_LIST',
174-
help='The payload list to use', required=True)
245+
help='The payload list to use for injection.', required=True)
175246
parser.add_argument(
176247
'-t', action='store', dest='REQUEST_DELAY',
177-
help='Amount of time in seconds to delay between requests')
248+
help='Amount of time (in seconds) to delay between requests.')
249+
parser.add_argument(
250+
'-s', '--screen', action='store', dest='SCREENSHOT_NAME',
251+
help='Enable screenshots of XSS hits.')
178252
parser.add_argument(
179-
'--screen', action='store', dest='SCREENSHOT_NAME',
180-
help='Screens of target')
253+
'-f', '--fuzzy', action='store', dest='FUZZY_DETECTION', type=int,
254+
const=50, nargs="?",
255+
help='Fuzzy detection rate of XSS [0 to 100 match] (default=50).')
181256

182257
arguments = parser.parse_args()
183258

0 commit comments

Comments
 (0)