├── .gitignore
├── LICENSE.txt
├── README.md
├── api
├── apiserver.py
├── logs
│ └── blank
├── models
│ ├── __init__.py
│ ├── collected_page.py
│ ├── initiate_database.py
│ ├── injection_record.py
│ ├── request_record.py
│ └── user.py
├── probe.js
├── requirements.txt
└── templates
│ ├── full_report_inline_view.htm
│ ├── image_401_load_template.htm
│ ├── image_load_template.htm
│ ├── markdown_template.md
│ ├── pgp_encrypted_template.txt
│ └── xss_email_template.htm
├── generate_config.py
└── gui
├── guiserver.py
├── requirements.txt
├── static
├── angularicons
│ ├── angularicons.eot
│ ├── angularicons.svg
│ ├── angularicons.ttf
│ └── angularicons.woff
├── css
│ ├── aboutus.css
│ ├── app.css
│ ├── bootstrap.min.css
│ ├── contactus.css
│ ├── cover.css
│ ├── docs.css
│ ├── features.css
│ ├── homepage.css
│ ├── main.css
│ ├── mainapp.css
│ ├── prettify.css
│ ├── pricing.css
│ ├── signup.css
│ ├── site.css
│ └── site.min.css
├── fonts
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ └── glyphicons-halflings-regular.woff
├── icons
│ ├── apple-touch-icon-114x114.png
│ ├── apple-touch-icon-120x120.png
│ ├── apple-touch-icon-144x144.png
│ ├── apple-touch-icon-152x152.png
│ ├── apple-touch-icon-180x180.png
│ ├── apple-touch-icon-57x57.png
│ ├── apple-touch-icon-72x72.png
│ ├── apple-touch-icon-76x76.png
│ └── apple-touch-icon.png
├── img
│ ├── Jumbotron.jpg
│ ├── ascii-logo.gif
│ ├── automated_payload_generation.png
│ ├── automatic_page_recon.png
│ ├── bg.png
│ ├── bootflat-ui-kit.jpg
│ ├── canvas_rendered_screenshots.png
│ ├── correlated_injections.png
│ ├── custom_xss_shortdomain.png
│ ├── design.png
│ ├── divider.png
│ ├── easy_markdown_generation_reports.png
│ ├── fast.png
│ ├── favicon.ico
│ ├── feature-bootstrap.png
│ ├── feature-css.png
│ ├── feature-lightweight.png
│ ├── feature-mobile.png
│ ├── footer-logo.png
│ ├── github.png
│ ├── index.png
│ ├── logo-index.png
│ ├── logo.png
│ ├── manage_all_your_injections.png
│ ├── photo-1.jpg
│ ├── photo-2.jpg
│ ├── photo-3.jpg
│ ├── photo-4.jpg
│ ├── prototyping.png
│ ├── report_email_alerts.png
│ ├── share.gif
│ ├── slider1.jpg
│ ├── slider2.jpg
│ ├── slider3.jpg
│ ├── thumbnail-1.jpg
│ ├── thumbnail-2.jpg
│ ├── thumbnail-3.jpg
│ ├── thumbnail-4.jpg
│ ├── together.png
│ ├── wild_flowers.png
│ ├── xss-hunter-report.png
│ └── xss_frontpage_alert.png
├── js
│ ├── app.js
│ ├── bootstrap.min.js
│ ├── clipboard.min.js
│ ├── contactus.js
│ ├── homepage.js
│ ├── html5shiv.js
│ ├── ie-emulation-modes-warning.js
│ ├── ie10-viewport-bug-workaround.js
│ ├── jquery-1.10.1.min.js
│ ├── jquery-2.1.4.min.js
│ ├── main.js
│ ├── navbar.js
│ ├── prettify.js
│ ├── register.js
│ ├── respond.min.js
│ ├── site.min.js
│ └── site.min.map
└── video
│ ├── xss_the_wrong_way.mp4
│ └── xss_the_wrong_way.webm
└── templates
├── contact.htm
├── features.htm
├── footer.htm
├── header.htm
├── homepage.htm
├── mainapp.htm
├── mainapp_collected_pages.htm
├── mainapp_payloads.htm
├── mainapp_settings.htm
├── mainapp_xss_fires.htm
├── navbar.htm
└── signup.htm
/.gitignore:
--------------------------------------------------------------------------------
1 | api/uploads/*
2 | api/logs/*
3 | gui/guienv/*
4 | *.pyc
5 | .DS_Store
6 | api/uploads/*
7 | env
8 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Matthew Bryant
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DEPRECATED - TRY THE NEW EXPRESS VERSION
2 |
3 | See here for more info: https://github.com/mandatoryprogrammer/xsshunter-express
4 |
5 | # XSS Hunter Source Code
6 | This is a portable version of the source code running on https://xsshunter.com. It is designed to be easily-installable on any server for security professionals and bug bounty hunters who wish to test for XSS in a much more powerful way.
7 |
8 | **If you don't want to set up this software and would rather just start testing, see https://xsshunter.com .**
9 |
10 | # Requirements
11 | * A server running (preferably) Ubuntu.
12 | * A [Mailgun](http://www.mailgun.com/) account, for sending out XSS payload fire emails.
13 | * A domain name, preferably something short to keep payload sizes down. Here is a good website for finding two letter domain names: [https://catechgory.com/](https://catechgory.com/). My domain is [xss.ht](xss.ht) for example.
14 | * A wildcard SSL certificate, [here's a cheap one](https://www.namecheap.com/security/ssl-certificates/wildcard.aspx). This is required because XSS Hunter identifies users based off of their sub-domains and they all need to be SSL-enabled. We can't use Let's Encrypt because [they don't support wildcard certificates](https://community.letsencrypt.org/t/frequently-asked-questions-faq/26). I'm going to hold off on insulting the CA business model, but rest assured it's very silly and costs them very little to mint you a wildcard certificate so go with the cheapest provider you can find (as long as it's supported in all browsers).
15 |
16 | # Setup
17 | Please see https://thehackerblog.com/xss-hunter-is-now-open-source-heres-how-to-set-it-up/ for information on how to set up XSS Hunter on your own server.
18 |
19 | # Summary of Functionality
20 | *Upon signing up you will create a special short domain such as `yoursubdomain.xss.ht` which identifies your XSS vulnerabilities and hosts your payload. You then use this subdomain in your XSS testing, using injection attempts such as `">`. XSS Hunter will automatically serve up XSS probes and collect the resulting information when they fire.*
21 |
22 | # Features
23 | * **Managed XSS payload fires**: Manage all of your XSS payloads in your XSS Hunter account's control panel.
24 | * **Powerful XSS Probes**: The following information is collected everytime a probe fires on a vulnerable page:
25 | * The vulnerable page's URI
26 | * Origin of Execution
27 | * The Victim's IP Address
28 | * The Page Referer
29 | * The Victim's User Agent
30 | * All Non-HTTP-Only Cookies
31 | * The Page's Full HTML DOM
32 | * Full Screenshot of the Affected Page
33 | * Responsible HTTP Request (If an XSS Hunter compatible tool is used)
34 | * **Full Page Screenshots**: XSS Hunter probes utilize the HTML5 canvas API to generate a full screenshot of the vulnerable page which an XSS payload has fired on. With this feature you can peak into internal administrative panels, support desks, logging systems, and other internal web apps. This allows for more powerful reports that show the full impact of the vulnerability to your client or bug bounty program.
35 | * **Markup Report Generation**: Each XSS payload report comes with a pre-generated markdown report. These generated reports are also compatible with other markdown-supporting platforms such as Phabricator for easy bug reporting on company ticketing systems.
36 | * **XSS Payload Fire Email Reports**: XSS payload fires also send out detailed email reports which can be easily forwarded to the appropriate security contacts for easy reporting of critical bugs.
37 | * **Automatic Payload Generation**: XSS Hunter automatically generates XSS payloads for you to use in your web application security testing.
38 | * **Correlated Injections**: Perhaps the most powerful feature of XSS Hunter is the ability to correlated injection attempts with XSS payload fires. By using an [XSS Hunter compatible testing tool](https://github.com/mandatoryprogrammer/xsshunter_client) you can know immediately what caused a specific payload to fire (even weeks after the injection attempt was made!).
39 | * **Option PGP Encryption for Payload Emails**: Extra paranoid? Client-side PGP encryption is available which will encrypt all injection data in the victim's browser before sending it off to the XSS Hunter service.
40 | * **Page Grabbing**: Upon your XSS payload firing you can specify a list of relative paths for the payload to automatically retrieve and store. This is useful in finding other vulnerabilities such as bad `crossdomain.xml` policies on internal systems which normally couldn't be accessed.
41 | * **Secondary Payload Loading**: Got a secondary payload that you want to load after XSS Hunter has done it's thing? XSS Hunter offers you the option to specify a secondary JavaScript payload to run after it's completed it's collection.
42 | * **iOS Web Application**: It is also possible to view your XSS payload fires via an iOS web app. Simple navigate to the `/app` path and save the page as a web application to your iPhone's desktop.
43 |
44 | # Notable Exploits
45 | * Blind XSS in Tesla's internal servicing tool: https://samcurry.net/cracking-my-windshield-and-earning-10000-on-the-tesla-bug-bounty-program/
46 | * Blind XSS in Spotify's Salesforce integration: https://mhmdiaa.github.io/blind-xss-in-spotify/
47 | * Blind XSS in GoDaddy's support panel: https://thehackerblog.com/poisoning-the-well-compromising-godaddy-customer-support-with-blind-xss/
48 |
49 | # Want to Contribute?
50 | All code was created by me and (for that reason) is likely *not* best practice and *definitely* in need of optimization/cleanup. Any pull requests are appreciated!
51 |
--------------------------------------------------------------------------------
/api/apiserver.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import tornado.template
3 | import logging.handlers
4 | import tornado.options
5 | import tornado.ioloop
6 | import dns.resolver
7 | import tornado.web
8 | import logging
9 | import binascii
10 | import unirest
11 | import urllib
12 | import copy
13 | import json
14 | import time
15 | import yaml
16 | import sys
17 | import os
18 | import io
19 |
20 | from models.initiate_database import *
21 | from tornado import gen
22 | from tornado import httpclient
23 | from models.user import User
24 | from models.injection_record import Injection
25 | from models.request_record import InjectionRequest
26 | from models.collected_page import CollectedPage
27 | from binascii import a2b_base64
28 |
29 | logging.basicConfig(filename="logs/detailed.log",level=logging.DEBUG)
30 |
31 | try:
32 | with open( '../config.yaml', 'r' ) as f:
33 | settings = yaml.safe_load( f )
34 | except IOError:
35 | print "Error reading config.yaml, have you created one? (Hint: Try running ./generate_config.py)"
36 | exit()
37 |
38 | CSRF_EXEMPT_ENDPOINTS = [ "/api/contactus", "/api/register", "/", "/api/login", "/health", "/favicon.ico", "/page_callback", "/api/record_injection" ]
39 | FORBIDDEN_SUBDOMAINS = [ "www", "api" ]
40 |
41 | with open( "probe.js", "r" ) as probe_handler:
42 | probejs = probe_handler.read()
43 |
44 | class BaseHandler(tornado.web.RequestHandler):
45 | def __init__(self, *args, **kwargs):
46 | super(BaseHandler, self).__init__(*args, **kwargs)
47 |
48 | if self.request.uri.startswith( "/api/" ):
49 | self.set_header("Content-Type", "application/json")
50 | else:
51 | self.set_header("Content-Type", "application/javascript")
52 |
53 | self.set_header("X-Frame-Options", "deny")
54 | self.set_header("Content-Security-Policy", "default-src 'self'")
55 | self.set_header("X-XSS-Protection", "1; mode=block")
56 | self.set_header("X-Content-Type-Options", "nosniff")
57 | self.set_header("Access-Control-Allow-Headers", "X-CSRF-Token, Content-Type")
58 | self.set_header("Access-Control-Allow-Origin", "https://www." + settings["domain"])
59 | self.set_header("Access-Control-Allow-Methods", "OPTIONS, PUT, DELETE, POST, GET")
60 | self.set_header("Access-Control-Allow-Credentials", "true")
61 | self.set_header("Cache-Control", "no-cache, no-store, must-revalidate")
62 | self.set_header("Pragma", "no-cache")
63 | self.set_header("Expires", "0")
64 | self.set_header("Server", "")
65 |
66 | self.request.remote_ip = self.request.headers.get( "X-Forwarded-For" )
67 |
68 | if not self.validate_csrf_token() and self.request.uri not in CSRF_EXEMPT_ENDPOINTS and not self.request.uri.startswith( "/b" ):
69 | self.error( "Invalid CSRF token provided!" )
70 | self.logit( "Someone did a request with an invalid CSRF token!", "warn")
71 | self.finish()
72 | return
73 |
74 | def logit( self, message, message_type="info" ):
75 | user_id = self.get_secure_cookie( "user" )
76 | if user_id != None:
77 | user = session.query( User ).filter_by( id=user_id ).first()
78 | if user != None:
79 | message = "[" + user.username + "]" + message
80 |
81 | message = "[" + self.request.remote_ip + "] " + message
82 |
83 | if message_type == "info":
84 | logging.info( message )
85 | elif message_type == "warn":
86 | logging.warn( message )
87 | elif message_type == "debug":
88 | logging.debug( message )
89 | else:
90 | logging.info( message )
91 |
92 | def options(self):
93 | pass
94 |
95 | # Hack to stop Tornado from sending the Etag header
96 | def compute_etag( self ):
97 | return None
98 |
99 | def throw_404( self ):
100 | self.set_status(404)
101 | self.write("Resource not found")
102 |
103 | def on_finish( self ):
104 | session.close()
105 |
106 | def validate_csrf_token( self ):
107 | csrf_token = self.get_secure_cookie( "csrf" )
108 |
109 | if csrf_token == None:
110 | return True
111 |
112 | if self.request.headers.get( 'X-CSRF-Token' ) == csrf_token:
113 | return True
114 |
115 | if self.get_argument( 'csrf', False ) == csrf_token:
116 | return True
117 |
118 | return False
119 |
120 | def validate_input( self, required_field_list, input_dict ):
121 | for field in required_field_list:
122 | if field not in input_dict:
123 | self.error( "Missing required field '" + field + "', this endpoint requires the following parameters: " + ', '.join( required_field_list ) )
124 | return False
125 | if input_dict[ field ] == "":
126 | self.error( "Missing required field '" + field + "', this endpoint requires the following parameters: " + ', '.join( required_field_list ) )
127 | return False
128 | return True
129 |
130 | def error( self, error_message ):
131 | self.write(json.dumps({
132 | "success": False,
133 | "error": error_message
134 | }))
135 |
136 | def get_authenticated_user( self ):
137 | user_id = self.get_secure_cookie( "user" )
138 | if user_id == None:
139 | self.error( "You must be authenticated to perform this action!" )
140 | return session.query( User ).filter_by( id=user_id ).first()
141 |
142 | def get_user_from_subdomain( self ):
143 | domain = self.request.headers.get( 'Host' )
144 | domain_parts = domain.split( "." + settings["domain"] )
145 | subdomain = domain_parts[0]
146 | return session.query( User ).filter_by( domain=subdomain ).first()
147 |
148 | def data_uri_to_file( data_uri ):
149 | """
150 | Turns the canvas data URI into a file handler
151 | """
152 | raw_base64 = data_uri.replace( 'data:image/png;base64,', '' )
153 | binary_data = a2b_base64( raw_base64 )
154 | f = io.BytesIO( binary_data )
155 | return f
156 |
157 | def pprint( input_dict ):
158 | print json.dumps(input_dict, sort_keys=True, indent=4, separators=(',', ': '))
159 |
160 | class GetXSSPayloadFiresHandler(BaseHandler):
161 | """
162 | Endpoint for querying for XSS payload fire data.
163 |
164 | By default returns past 25 payload fires
165 |
166 | Params:
167 | offset
168 | limit
169 | """
170 | def get( self ):
171 | self.logit( "User retrieved their injection results" )
172 | user = self.get_authenticated_user()
173 | offset = abs( int( self.get_argument('offset', default=0 ) ) )
174 | limit = abs( int( self.get_argument('limit', default=25 ) ) )
175 | results = session.query( Injection ).filter_by( owner_id = user.id ).order_by( Injection.injection_timestamp.desc() ).limit( limit ).offset( offset )
176 | total = session.query( Injection ).filter_by( owner_id = user.id ).count()
177 |
178 | return_list = []
179 |
180 | for result in results:
181 | return_list.append( result.get_injection_blob() )
182 |
183 | return_dict = {
184 | "results": return_list,
185 | "total": total,
186 | "success": True
187 | }
188 | self.write( json.dumps( return_dict ) )
189 |
190 | def upload_screenshot( base64_screenshot_data_uri ):
191 | screenshot_filename = "uploads/xsshunter_screenshot_" + binascii.hexlify( os.urandom( 100 ) ) + ".png"
192 | screenshot_file_handler = data_uri_to_file( base64_screenshot_data_uri )
193 | local_file_handler = open( screenshot_filename, "w" ) # Async IO http://stackoverflow.com/a/13644499/1195812
194 | local_file_handler.write( screenshot_file_handler.read() )
195 | local_file_handler.close()
196 | return screenshot_filename
197 |
198 | def record_callback_in_database( callback_data, request_handler ):
199 | screenshot_file_path = upload_screenshot( callback_data["screenshot"] )
200 |
201 | injection = Injection( vulnerable_page=callback_data["uri"].encode("utf-8"),
202 | victim_ip=callback_data["ip"].encode("utf-8"),
203 | referer=callback_data["referrer"].encode("utf-8"),
204 | user_agent=callback_data["user-agent"].encode("utf-8"),
205 | cookies=callback_data["cookies"].encode("utf-8"),
206 | dom=callback_data["dom"].encode("utf-8"),
207 | origin=callback_data["origin"].encode("utf-8"),
208 | screenshot=screenshot_file_path.encode("utf-8"),
209 | injection_timestamp=int(time.time()),
210 | browser_time=int(callback_data["browser-time"])
211 | )
212 | injection.generate_injection_id()
213 | owner_user = request_handler.get_user_from_subdomain()
214 | injection.owner_id = owner_user.id
215 |
216 | # Check if this is correlated to someone's request.
217 | if callback_data["injection_key"] != "[PROBE_ID]":
218 | correlated_request_entry = session.query( InjectionRequest ).filter_by( injection_key=callback_data["injection_key"] ).filter_by( owner_correlation_key=owner_user.owner_correlation_key ).first()
219 |
220 | if correlated_request_entry != None:
221 | injection.correlated_request = correlated_request_entry.request
222 | else:
223 | injection.correlated_request = "Could not correlate XSS payload fire with request!"
224 |
225 | session.add( injection )
226 | session.commit()
227 |
228 | return injection
229 |
230 | def email_sent_callback( response ):
231 | print response.body
232 |
233 | def send_email( to, subject, body, attachment_file, body_type="html" ):
234 | if body_type == "html":
235 | body += " " # I'm so sorry.
236 |
237 | email_data = {
238 | "from": urllib.quote_plus( settings["email_from"] ),
239 | "to": urllib.quote_plus( to ),
240 | "subject": urllib.quote_plus( subject ),
241 | body_type: urllib.quote_plus( body ),
242 | }
243 |
244 | thread = unirest.post( "https://api.mailgun.net/v3/" + settings["mailgun_sending_domain"] + "/messages",
245 | headers={"Accept": "application/json"},
246 | params=email_data,
247 | auth=("api", settings["mailgun_api_key"] ),
248 | callback=email_sent_callback)
249 |
250 | def send_javascript_pgp_encrypted_callback_message( email_data, email ):
251 | return send_email( email, "[XSS Hunter] XSS Payload Message (PGP Encrypted)", email_data, False, "text" )
252 |
253 | def send_javascript_callback_message( email, injection_db_record ):
254 | loader = tornado.template.Loader( "templates/" )
255 |
256 | injection_data = injection_db_record.get_injection_blob()
257 |
258 | email_html = loader.load( "xss_email_template.htm" ).generate( injection_data=injection_data, domain=settings["domain"] )
259 | return send_email( email, "[XSS Hunter] XSS Payload Fired On " + injection_data['vulnerable_page'], email_html, injection_db_record.screenshot )
260 |
261 | class UserInformationHandler(BaseHandler):
262 | def get(self):
263 | user = self.get_authenticated_user()
264 | self.logit( "User grabbed their profile information" )
265 | if user == None:
266 | return
267 | self.write( json.dumps( user.get_user_blob() ) )
268 |
269 | def put(self):
270 | user = self.get_authenticated_user()
271 | if user == None:
272 | return
273 |
274 | user_data = json.loads(self.request.body)
275 |
276 | # Mass assignment is dangerous mmk
277 | allowed_attributes = ["pgp_key", "full_name", "email", "password", "email_enabled", "chainload_uri", "page_collection_paths_list" ]
278 | invalid_attribute_list = []
279 | tmp_domain = user.domain
280 | for key, value in user_data.iteritems():
281 | if key in allowed_attributes:
282 | return_data = user.set_attribute( key, user_data.get( key ) )
283 | if return_data != True:
284 | invalid_attribute_list.append( key )
285 |
286 | session.commit()
287 |
288 | return_data = user.get_user_blob()
289 |
290 | if invalid_attribute_list:
291 | return_data["success"] = False
292 | return_data["invalid_fields"] = invalid_attribute_list
293 | else:
294 | self.logit( "User just updated their profile information." )
295 | return_data["success"] = True
296 |
297 | self.write( json.dumps( return_data ) )
298 |
299 | def authenticate_user( request_handler, in_username ):
300 | user = session.query( User ).filter_by( username=in_username ).first()
301 |
302 | csrf_token = binascii.hexlify( os.urandom( 50 ) )
303 | request_handler.set_secure_cookie( "user", user.id, httponly=True )
304 | request_handler.set_secure_cookie( "csrf", csrf_token, httponly=True )
305 | request_handler.write(json.dumps({
306 | "success": True,
307 | "csrf_token": csrf_token,
308 | }))
309 |
310 | class RegisterHandler(BaseHandler):
311 | @gen.coroutine
312 | def post(self):
313 | user_data = json.loads(self.request.body)
314 | user_data["email_enabled"] = True
315 | if not self.validate_input( ["email","username","password", "domain"], user_data ):
316 | return
317 |
318 | if session.query( User ).filter_by( username=user_data.get( "username" ) ).first():
319 | return_dict = {
320 | "success": False,
321 | "invalid_fields": ["username (already registered!)"],
322 | }
323 | self.write( json.dumps( return_dict ) )
324 | return
325 |
326 | domain = user_data.get( "domain" )
327 | if session.query( User ).filter_by( domain=domain ).first() or domain in FORBIDDEN_SUBDOMAINS:
328 | return_dict = {
329 | "success": False,
330 | "invalid_fields": ["domain (already registered!)"],
331 | }
332 | self.write( json.dumps( return_dict ) )
333 | return
334 |
335 | new_user = User()
336 |
337 | return_dict = {}
338 | allowed_attributes = ["pgp_key", "full_name", "domain", "email", "password", "username", "email_enabled" ]
339 | invalid_attribute_list = []
340 | for key, value in user_data.iteritems():
341 | if key in allowed_attributes:
342 | return_data = new_user.set_attribute( key, user_data.get( key ) )
343 | if return_data != True:
344 | invalid_attribute_list.append( key )
345 |
346 | new_user.generate_user_id()
347 |
348 | if invalid_attribute_list:
349 | return_dict["success"] = False
350 | return_dict["invalid_fields"] = invalid_attribute_list
351 | return_dict = {
352 | "success": False,
353 | "invalid_fields": ["username (already registered!)"],
354 | }
355 | self.write( json.dumps( return_dict ) )
356 | return
357 |
358 | self.logit( "New user successfully registered with username of " + user_data["username"] )
359 | session.add( new_user )
360 | session.commit()
361 |
362 | authenticate_user( self, user_data.get( "username" ) )
363 | return
364 |
365 | class LoginHandler(BaseHandler):
366 | @gen.coroutine
367 | def post(self):
368 | user_data = json.loads(self.request.body)
369 | if not self.validate_input( ["username","password"], user_data ):
370 | return
371 |
372 | user = session.query( User ).filter_by( username=user_data.get( "username" ) ).first()
373 |
374 | if user is None:
375 | self.error( "Invalid username or password supplied" )
376 | self.logit( "Someone failed to log in as " + user_data["username"], "warn" )
377 | return
378 | elif user.compare_password( user_data.get( "password" ) ):
379 | authenticate_user( self, user_data.get( "username" ) )
380 | self.logit( "Someone logged in as " + user_data["username"] )
381 | return
382 | self.error( "Invalid username or password supplied" )
383 | return
384 |
385 | class CallbackHandler(BaseHandler):
386 | """
387 | This is the handler that receives the XSS payload data upon it firing in someone's browser, it contains things such as session cookies, the page DOM, a screenshot of the page, etc.
388 | """
389 |
390 | def post( self ):
391 | self.set_header( 'Access-Control-Allow-Origin', '*' )
392 | self.set_header( 'Access-Control-Allow-Methods', 'POST, GET, HEAD, OPTIONS' )
393 | self.set_header( 'Access-Control-Allow-Headers', 'X-Requested-With' )
394 |
395 | owner_user = self.get_user_from_subdomain()
396 |
397 | if owner_user == None:
398 | self.throw_404()
399 | return
400 |
401 | if "-----BEGIN PGP MESSAGE-----" in self.request.body:
402 | if owner_user.email_enabled:
403 | self.logit( "User " + owner_user.username + " just got a PGP encrypted XSS callback, passing it along." )
404 | send_javascript_pgp_encrypted_callback_message( self.request.body, owner_user.email )
405 | else:
406 | callback_data = json.loads( self.request.body )
407 | callback_data['ip'] = self.request.remote_ip
408 | injection_db_record = record_callback_in_database( callback_data, self )
409 | self.logit( "User " + owner_user.username + " just got an XSS callback for URI " + injection_db_record.vulnerable_page )
410 |
411 | if owner_user.email_enabled:
412 | send_javascript_callback_message( owner_user.email, injection_db_record )
413 | self.write( '{}' )
414 |
415 | class HomepageHandler(BaseHandler):
416 | def get(self, path):
417 |
418 | self.set_header("Access-Control-Allow-Origin", "*")
419 | self.set_header("Access-Control-Allow-Methods", "OPTIONS, PUT, DELETE, POST, GET")
420 | self.set_header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Origin, Authorization, Accept, Accept-Encoding")
421 |
422 | domain = self.request.headers.get( 'Host' )
423 |
424 | user = self.get_user_from_subdomain()
425 |
426 | if user == None:
427 | self.throw_404()
428 | return
429 |
430 | new_probe = probejs
431 | new_probe = new_probe.replace( '[HOST_URL]', "https://" + domain )
432 | new_probe = new_probe.replace( '[PGP_REPLACE_ME]', json.dumps( user.pgp_key ) )
433 | new_probe = new_probe.replace( '[CHAINLOAD_REPLACE_ME]', json.dumps( user.chainload_uri ) )
434 | new_probe = new_probe.replace( '[COLLECT_PAGE_LIST_REPLACE_ME]', json.dumps( user.get_page_collection_path_list() ) )
435 |
436 | if user.pgp_key != "":
437 | with open( "templates/pgp_encrypted_template.txt", "r" ) as template_handler:
438 | new_probe = new_probe.replace( '[TEMPLATE_REPLACE_ME]', json.dumps( template_handler.read() ))
439 | else:
440 | new_probe = new_probe.replace( '[TEMPLATE_REPLACE_ME]', json.dumps( "" ))
441 |
442 | if self.request.uri != "/":
443 | probe_id = self.request.uri.split( "/" )[1]
444 | self.write( new_probe.replace( "[PROBE_ID]", probe_id ) )
445 | else:
446 | self.write( new_probe )
447 |
448 | class ContactUsHandler(BaseHandler):
449 | def post( self ):
450 | contact_data = json.loads(self.request.body)
451 | if not self.validate_input( ["name","email", "body"], contact_data ):
452 | return
453 |
454 | self.logit( "Someone just used the 'Contact Us' form." )
455 |
456 | email_body = "Name: " + contact_data["name"] + "\n"
457 | email_body += "Email: " + contact_data["email"] + "\n"
458 | email_body += "Message: " + contact_data["body"] + "\n"
459 | send_email( settings["abuse_email"], "XSSHunter Contact Form Submission", email_body, "", "text" )
460 |
461 | self.write({
462 | "success": True,
463 | })
464 |
465 | class ResendInjectionEmailHandler(BaseHandler):
466 | def post( self ):
467 | post_data = json.loads(self.request.body)
468 |
469 | if not self.validate_input( ["id"], post_data ):
470 | return
471 |
472 | injection_db_record = session.query( Injection ).filter_by( id=str( post_data.get( "id" ) ) ).first()
473 | user = self.get_authenticated_user()
474 |
475 | if injection_db_record.owner_id != user.id:
476 | self.logit( "Just tried to resend an injection email that wasn't theirs! (ID:" + post_data["id"] + ")", "warn")
477 | self.error( "Fuck off <3" )
478 | return
479 |
480 | self.logit( "User just requested to resend the injection record email for URI " + injection_db_record.vulnerable_page )
481 |
482 | send_javascript_callback_message( user.email, injection_db_record )
483 |
484 | self.write({
485 | "success": True,
486 | "message": "Email sent!",
487 | })
488 |
489 | class DeleteInjectionHandler(BaseHandler):
490 | def delete( self ):
491 | delete_data = json.loads(self.request.body)
492 |
493 | if not self.validate_input( ["id"], delete_data ):
494 | return
495 |
496 | injection_db_record = session.query( Injection ).filter_by( id=str( delete_data.get( "id" ) ) ).first()
497 | user = self.get_authenticated_user()
498 |
499 | if injection_db_record.owner_id != user.id:
500 | self.logit( "Just tried to delete an injection email that wasn't theirs! (ID:" + delete_data["id"] + ")", "warn")
501 | self.error( "Fuck off <3" )
502 | return
503 |
504 | self.logit( "User delted injection record with an id of " + injection_db_record.id + "(" + injection_db_record.vulnerable_page + ")")
505 |
506 | os.remove( injection_db_record.screenshot )
507 |
508 | injection_db_record = session.query( Injection ).filter_by( id=str( delete_data.get( "id" ) ) ).delete()
509 | session.commit()
510 |
511 | self.write({
512 | "success": True,
513 | "message": "Injection deleted!",
514 | })
515 |
516 | class HealthHandler(BaseHandler):
517 | def get( self ):
518 | try:
519 | injection_db_record = session.query( Injection ).filter_by( id="test" ).limit( 1 )
520 | self.write( "XSSHUNTER_OK" )
521 | except:
522 | self.write( "ERROR" )
523 | self.set_status(500)
524 |
525 | class LogoutHandler( BaseHandler ):
526 | def get( self ):
527 | self.logit( "User is logging out." )
528 | self.clear_cookie("user")
529 | self.clear_cookie("csrf")
530 | self.write( "{}" )
531 |
532 | class InjectionRequestHandler( BaseHandler ):
533 | """
534 | This endpoint is for recording injection attempts.
535 |
536 | It requires the following parameters:
537 |
538 | request - This is the request (note: NOT specific to HTTP) which was performed to attempt the injection.
539 | owner_correlation_key - This is a private key which is used to link the injection to a specific user - displayed in the settings panel.
540 | injection_key - This is the injection key which the XSS payload uses to identify itself to the XSS Hunter service ( where aiwlq is the key )
541 |
542 | Sending two correlation requests means that the previous injection_key entry will be replaced.
543 | """
544 | def post( self ):
545 | return_data = {}
546 | request_dict = json.loads( self.request.body )
547 | if not self.validate_input( ["request", "owner_correlation_key", "injection_key"], request_dict ):
548 | return
549 |
550 | injection_key = request_dict.get( "injection_key" )
551 |
552 | injection_request = InjectionRequest()
553 | injection_request.injection_key = injection_key
554 | injection_request.request = request_dict.get( "request" )
555 | owner_correlation_key = request_dict.get( "owner_correlation_key" )
556 | injection_request.owner_correlation_key = owner_correlation_key
557 |
558 | # Ensure that this is an existing correlation key
559 | owner_user = session.query( User ).filter_by( owner_correlation_key=owner_correlation_key ).first()
560 | if owner_user is None:
561 | return_data["success"] = False
562 | return_data["message"] = "Invalid owner correlation key provided!"
563 | self.write( json.dumps( return_data ) )
564 | return
565 |
566 | self.logit( "User " + owner_user.username + " just sent us an injection attempt with an ID of " + injection_request.injection_key )
567 |
568 | # Replace any previous injections with the same key and owner
569 | session.query( InjectionRequest ).filter_by( injection_key=injection_key ).filter_by( owner_correlation_key=owner_correlation_key ).delete()
570 |
571 | return_data["success"] = True
572 | return_data["message"] = "Injection request successfully recorded!"
573 | session.add( injection_request )
574 | session.commit()
575 | self.write( json.dumps( return_data ) )
576 |
577 | class CollectPageHandler( BaseHandler ):
578 | def post( self ):
579 | self.set_header( 'Access-Control-Allow-Origin', '*' )
580 | self.set_header( 'Access-Control-Allow-Methods', 'POST, GET, HEAD, OPTIONS' )
581 | self.set_header( 'Access-Control-Allow-Headers', 'X-Requested-With' )
582 |
583 | user = self.get_user_from_subdomain()
584 | request_dict = json.loads( self.request.body )
585 | if not self.validate_input( ["page_html", "uri"], request_dict ):
586 | return
587 |
588 | if user == None:
589 | self.throw_404()
590 | return
591 |
592 | page = CollectedPage()
593 | page.uri = request_dict.get( "uri" )
594 | page.page_html = request_dict.get( "page_html" )
595 | page.owner_id = user.id
596 | page.timestamp = int(time.time())
597 |
598 | self.logit( "Received a collected page for user " + user.username + " with a URI of " + page.uri )
599 |
600 | session.add( page )
601 | session.commit()
602 |
603 | class GetCollectedPagesHandler( BaseHandler ):
604 | """
605 | Endpoint for querying for collected pages.
606 |
607 | By default returns past 25 payload fires
608 |
609 | Params:
610 | offset
611 | limit
612 | """
613 | def get( self ):
614 | user = self.get_authenticated_user()
615 | offset = abs( int( self.get_argument('offset', default=0 ) ) )
616 | limit = abs( int( self.get_argument('limit', default=25 ) ) )
617 | results = session.query( CollectedPage ).filter_by( owner_id = user.id ).order_by( CollectedPage.timestamp.desc() ).limit( limit ).offset( offset )
618 | total = session.query( CollectedPage ).filter_by( owner_id = user.id ).count()
619 |
620 | self.logit( "User is retrieving collected pages.")
621 |
622 | return_list = []
623 |
624 | for result in results:
625 | return_list.append( result.to_dict() )
626 |
627 | return_dict = {
628 | "results": return_list,
629 | "total": total,
630 | "success": True
631 | }
632 | self.write( json.dumps( return_dict ) )
633 |
634 | class DeleteCollectedPageHandler(BaseHandler):
635 | def delete( self ):
636 | delete_data = json.loads(self.request.body)
637 |
638 | if not self.validate_input( ["id"], delete_data ):
639 | return
640 |
641 | collected_page_db_record = session.query( CollectedPage ).filter_by( id=str( delete_data.get( "id" ) ) ).first()
642 | user = self.get_authenticated_user()
643 |
644 | if collected_page_db_record.owner_id != user.id:
645 | self.logit( "Just tried to delete a collected page that wasn't theirs! (ID:" + delete_data["id"] + ")", "warn")
646 | self.error( "Fuck off <3" )
647 | return
648 |
649 | self.logit( "User is deleting collected page with the URI of " + collected_page_db_record.uri )
650 | collected_page_db_record = session.query( CollectedPage ).filter_by( id=str( delete_data.get( "id" ) ) ).delete()
651 | session.commit()
652 |
653 | self.write({
654 | "success": True,
655 | "message": "Collected page deleted!",
656 | })
657 |
658 | def make_app():
659 | return tornado.web.Application([
660 | (r"/api/register", RegisterHandler),
661 | (r"/api/login", LoginHandler),
662 | (r"/api/collected_pages", GetCollectedPagesHandler),
663 | (r"/api/delete_injection", DeleteInjectionHandler),
664 | (r"/api/delete_collected_page", DeleteCollectedPageHandler),
665 | (r"/api/user", UserInformationHandler),
666 | (r"/api/payloadfires", GetXSSPayloadFiresHandler),
667 | (r"/api/contactus", ContactUsHandler),
668 | (r"/api/resend_injection_email", ResendInjectionEmailHandler),
669 | (r"/api/logout", LogoutHandler),
670 | (r"/js_callback", CallbackHandler),
671 | (r"/page_callback", CollectPageHandler),
672 | (r"/health", HealthHandler),
673 | (r"/uploads/(.*)", tornado.web.StaticFileHandler, {"path": "uploads/"}),
674 | (r"/api/record_injection", InjectionRequestHandler),
675 | (r"/(.*)", HomepageHandler),
676 | ], cookie_secret=settings["cookie_secret"])
677 |
678 | if __name__ == "__main__":
679 | args = sys.argv
680 | args.append("--log_file_prefix=logs/access.log")
681 | tornado.options.parse_command_line(args)
682 | Base.metadata.create_all(engine)
683 | app = make_app()
684 | app.listen( 8888 )
685 | tornado.ioloop.IOLoop.current().start()
686 |
--------------------------------------------------------------------------------
/api/logs/blank:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/api/logs/blank
--------------------------------------------------------------------------------
/api/models/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/api/models/__init__.py
--------------------------------------------------------------------------------
/api/models/collected_page.py:
--------------------------------------------------------------------------------
1 | from initiate_database import *
2 | import binascii
3 | import bcrypt
4 | import os
5 |
6 | class CollectedPage(Base):
7 | __tablename__ = 'collected_pages'
8 |
9 | id = Column(String(100), primary_key=True)
10 | uri = Column(Text())
11 | page_html = Column(Text())
12 | owner_id = Column(String(100))
13 | timestamp = Column(Integer())
14 |
15 | def __init__( self ):
16 | self.generate_injection_id()
17 |
18 | def generate_injection_id( self ):
19 | self.id = binascii.hexlify(os.urandom(50))
20 |
21 | def to_dict( self ):
22 | exposed_attributes = [ "uri", "id", "page_html", "timestamp" ]
23 | return_dict = {}
24 |
25 | for attribute in exposed_attributes:
26 | return_dict[ attribute ] = getattr( self, attribute )
27 |
28 | return return_dict
29 |
30 | def __str__( self ):
31 | return self.id
32 |
--------------------------------------------------------------------------------
/api/models/initiate_database.py:
--------------------------------------------------------------------------------
1 | import yaml
2 | try:
3 | with open( '../config.yaml', 'r' ) as f:
4 | settings = yaml.safe_load( f )
5 | except IOError:
6 | print "INITIATEDB: Error reading config.yaml, have you created one? (Hint: Try running ./generate_config.py)"
7 | exit()
8 |
9 | from sqlalchemy import create_engine
10 | engine = create_engine('postgresql://' + settings["postgreql_username"] + ':' + settings["postgreql_password"] + '@localhost/' + settings["postgres_db"] + '?client_encoding=utf8', pool_recycle=60, encoding='utf8')
11 | from sqlalchemy.ext.declarative import declarative_base
12 | Base = declarative_base()
13 | from sqlalchemy import Column, Integer, String, func, update, Text, Binary, Boolean, BigInteger, event, select, exc
14 | from sqlalchemy.orm import sessionmaker, scoped_session
15 | Session = scoped_session(sessionmaker(bind=engine))
16 | session = Session()
17 |
--------------------------------------------------------------------------------
/api/models/injection_record.py:
--------------------------------------------------------------------------------
1 | from initiate_database import *
2 | import binascii
3 | import bcrypt
4 | import os
5 |
6 | class Injection(Base):
7 | __tablename__ = 'injections'
8 |
9 | id = Column(String(100), primary_key=True)
10 | type = Column(String(100)) # JavaScript/Image
11 | injection_timestamp = Column(Integer())
12 | vulnerable_page = Column(String(3000))
13 | victim_ip = Column(String(100))
14 | referer = Column(String(3000))
15 | user_agent = Column(String(3000))
16 | cookies = Column(String(5000))
17 | dom = Column(Text())
18 | origin = Column(String(300))
19 | screenshot = Column(String(300))
20 | owner_id = Column(String(100))
21 | browser_time = Column(BigInteger())
22 | correlated_request = Column(Text())
23 |
24 | def generate_injection_id( self ):
25 | self.id = binascii.hexlify(os.urandom(50))
26 |
27 | def get_injection_blob( self ):
28 | exposed_attributes = [ "id", "vulnerable_page", "victim_ip", "referer", "user_agent", "cookies", "dom", "origin", "screenshot", "injection_timestamp", "correlated_request", "browser_time" ]
29 | return_dict = {}
30 |
31 | for attribute in exposed_attributes:
32 | return_dict[ attribute ] = getattr( self, attribute )
33 |
34 | return return_dict
35 |
36 | def __str__( self ):
37 | return self.vulnerable_page
38 |
--------------------------------------------------------------------------------
/api/models/request_record.py:
--------------------------------------------------------------------------------
1 | from initiate_database import *
2 | import binascii
3 | import bcrypt
4 | import time
5 | import os
6 |
7 | class InjectionRequest(Base):
8 | __tablename__ = 'injection_requests'
9 |
10 | id = Column(String(100), primary_key=True)
11 | request = Column(Text())
12 | injection_key = Column(String(100))
13 | owner_correlation_key = Column(String(100))
14 | timestamp = Column(Integer())
15 |
16 | def __init__( self ):
17 | self.generate_injection_id()
18 | self.timestamp = int( time.time() )
19 |
20 | def generate_injection_id( self ):
21 | self.id = binascii.hexlify(os.urandom(50))
22 |
23 | def get_injection_blob( self ):
24 | exposed_attributes = [ "request", "injection_key" ]
25 | return_dict = {}
26 |
27 | for attribute in exposed_attributes:
28 | return_dict[ attribute ] = getattr( self, attribute )
29 |
30 | return return_dict
31 |
32 | def __str__( self ):
33 | return self.id
34 |
--------------------------------------------------------------------------------
/api/models/user.py:
--------------------------------------------------------------------------------
1 | from initiate_database import *
2 | from urlparse import urlparse
3 | import binascii
4 | import bcrypt
5 | import os
6 | import re
7 |
8 | class User(Base):
9 | __tablename__ = 'users'
10 |
11 | id = Column(String(100), primary_key=True)
12 | full_name = Column(String(120))
13 | username = Column(String(80))
14 | password = Column(String(120))
15 | email = Column(String(120))
16 | domain = Column(String(120))
17 | pgp_key = Column(Text())
18 | password_reset_token = Column(String(120))
19 | is_premium = Column(Boolean())
20 | email_enabled = Column(Boolean())
21 | chainload_uri = Column(Text())
22 | owner_correlation_key = Column(String(100))
23 | page_collection_paths_list = Column(Text()) # Done this way to allow users to just paste and share relative page lists
24 |
25 | def __init__( self ):
26 | self.generate_user_id()
27 | self.generate_owner_correlation_key()
28 |
29 | def set_fullname( self, in_fullname ):
30 | self.full_name = str( in_fullname ).strip()
31 | return True
32 |
33 | def set_username( self, in_username ):
34 | self.username = str( in_username ).strip()
35 | return True
36 |
37 | def set_password( self, in_password ):
38 | self.password = self._get_bcrypt_hash( in_password )
39 | return True
40 |
41 | def set_pgp_key( self, in_pgp_key ):
42 | self.pgp_key = str( in_pgp_key ).strip()
43 | return True
44 |
45 | def set_email( self, in_email ):
46 | in_email = str( in_email ).strip()
47 | if bool( re.search( r"^[A-Za-z0-9\.\+_-]+@[A-Za-z0-9\._-]+\.[a-zA-Z]*$", in_email, flags=0 ) ):
48 | self.email = in_email
49 | return True
50 | return False
51 |
52 | def set_domain( self, set_domain ):
53 | if self.domain == set_domain:
54 | return True
55 |
56 | if not bool( re.search( r"^[A-Za-z0-9]+$", set_domain, flags=0 ) ):
57 | return False
58 |
59 | set_domain = str( set_domain ).strip()
60 | domain_exists = session.query( User ).filter_by( domain=set_domain ).first()
61 |
62 | if domain_exists == None:
63 | self.domain = set_domain
64 | return True
65 | return False
66 |
67 | def set_email_enabled( self, in_email_enabled ):
68 | self.email_enabled = in_email_enabled
69 | return True
70 |
71 | def set_chainload_uri( self, in_chainload_uri ):
72 | parsed_url = urlparse( in_chainload_uri )
73 | if bool( parsed_url.scheme ) or in_chainload_uri == "":
74 | self.chainload_uri = in_chainload_uri
75 | return True
76 | return False
77 |
78 | def set_page_collection_paths_list( self, in_paths_list_text ):
79 | self.page_collection_paths_list = in_paths_list_text.strip()
80 | return True
81 |
82 | def set_attribute( self, attribute, value ):
83 | if attribute == "password":
84 | return self.set_password( value )
85 | if attribute == "full_name":
86 | return self.set_fullname( value )
87 | if attribute == "username":
88 | return self.set_username( value )
89 | if attribute == "email":
90 | return self.set_email( value )
91 | if attribute == "domain":
92 | return self.set_domain( value )
93 | if attribute == "pgp_key":
94 | return self.set_pgp_key( value )
95 | if attribute == "email_enabled":
96 | return self.set_email_enabled( value )
97 | if attribute == "chainload_uri":
98 | return self.set_chainload_uri( value )
99 | if attribute == "page_collection_paths_list":
100 | return self.set_page_collection_paths_list( value )
101 |
102 | def get_page_collection_path_list( self ):
103 | if self.page_collection_paths_list == None:
104 | return []
105 |
106 | tmp_pages_list = self.page_collection_paths_list.split( "\n" )
107 | page_list = []
108 |
109 | for page in tmp_pages_list:
110 | page = page.strip()
111 | if page != "":
112 | page_list.append( page )
113 |
114 | return page_list
115 |
116 | def get_user_blob( self ):
117 | exposed_attributes = [ "full_name", "email", "username", "pgp_key", "domain", "email_enabled", "chainload_uri", "owner_correlation_key", "page_collection_paths_list" ]
118 | return_dict = {}
119 |
120 | for attribute in exposed_attributes:
121 | return_dict[ attribute ] = getattr( self, attribute )
122 |
123 | return return_dict
124 |
125 | def generate_user_id( self ):
126 | self.id = binascii.hexlify(os.urandom(50))
127 |
128 | def generate_password_reset_key( self ):
129 | self.password_reset_token = binascii.hexlify(os.urandom(60))
130 |
131 | def generate_owner_correlation_key( self ):
132 | self.owner_correlation_key = binascii.hexlify(os.urandom(50))
133 |
134 | def compare_password( self, in_password ):
135 | return ( bcrypt.hashpw( str( in_password.encode( 'utf-8' ) ), str( self.password.encode( 'utf-8' ) ) ) == self.password )
136 |
137 | def update( self ):
138 | session.commit()
139 |
140 | def _get_bcrypt_hash( self, input_string ):
141 | return bcrypt.hashpw( str( input_string ), bcrypt.gensalt( 10 ) )
142 |
143 | def __str__(self):
144 | return self.username + " - ( " + self.full_name + " )"
145 |
--------------------------------------------------------------------------------
/api/requirements.txt:
--------------------------------------------------------------------------------
1 | PyYAML==3.11
2 | SQLAlchemy==1.0.13
3 | Unirest==1.1.7
4 | argparse==1.2.1
5 | backports-abc==0.4
6 | backports.ssl-match-hostname==3.5.0.1
7 | bcrypt==2.0.0
8 | certifi==2016.2.28
9 | cffi==1.6.0
10 | dnspython==1.14.0
11 | poster==0.8.1
12 | psycopg2==2.6.1
13 | pycparser==2.14
14 | singledispatch==3.4.0.3
15 | six==1.10.0
16 | tornado==4.3
17 | wsgiref==0.1.2
18 |
--------------------------------------------------------------------------------
/api/templates/full_report_inline_view.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Vulnerable Page URL
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
User IP Address
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
Referer
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
User Agent
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Cookies
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
Injection Point (Raw HTTP Request)
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
DOM
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
Execution Origin
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/api/templates/image_401_load_template.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | XSS Hunter Report
10 | This report has been generated by an XSS Hunter server and contains the details of an authenticated image load which could indicate the existance of a cross-site scripting vulnerability (or content spoofing).
11 |
12 |
13 |
14 |
15 |
User IP Address
16 |
17 |
20 |
21 |
22 |
23 |
24 |
Credentials
25 |
26 |
27 | Username: {{ callback_data['basic_username'] }}
28 | Password: {{ callback_data['basic_password'] }}
29 |
30 |
31 |
32 |
33 |
34 |
Referer
35 |
36 |
39 |
40 |
41 |
42 |
43 |
User-Agent
44 |
45 |
46 | {{ callback_data['user-agent'] }}
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/api/templates/image_load_template.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | XSS Hunter Report
10 | This report has been generated by a XSS Hunter server and contains the details of a image load which could indicate the existance of a cross-site scripting vulnerability (or content spoofing).
11 |
12 |
13 |
14 |
15 |
User IP Address
16 |
17 |
20 |
21 |
22 |
23 |
24 |
Referer
25 |
26 |
29 |
30 |
31 |
32 |
33 |
User-Agent
34 |
35 |
36 | {{ callback_data['user-agent'] }}
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/api/templates/markdown_template.md:
--------------------------------------------------------------------------------
1 | # XSSHunter Report
2 |
3 | The page located at `{{vulnerable_page}}` suffers from a Cross-site Scripting (XSS) vulnerability. XSS is a vulnerability which occurs when user input is unsafely encorporated into the HTML markup inside of a webpage. When not properly escaped an attacker can inject malicious JavaScript that, once evaluated, can be used to hijack authenticated sessions and rewrite the vulnerable page's layout and functionality. The following report contains information on an XSS payload that has fired on `{{origin}}`, it can be used to reproduce and remediate the vulnerability.
4 |
5 | ### XSS Payload Fire Details
6 | ##### Vulnerable Page
7 | `{{vulnerable_page}}`
8 |
9 | ##### Victim IP Address
10 | `{{victim_ip}}`
11 |
12 | ##### Referer
13 | `{{referer}}`
14 |
15 | ##### User Agent
16 | `{{user_agent}}`
17 |
18 | ##### Cookies (Non-HTTPOnly)
19 | `{{cookies}}`
20 |
21 | ##### Document Object Model
22 | `{{dom}}`
23 |
24 | ##### Origin
25 | `{{origin}}`
26 |
27 | ##### HTML5 Canvas-Rendered Screenshot
28 | 
29 |
30 | ##### Injection Timestamp
31 | `{{injection_timestamp}}`
32 |
33 | ## Remediation
34 | For more information about Cross-site Scripting and remediation of the issue, see the following resources:
35 | * [Cross-site Scripting (XSS) - OWASP](https://www.owasp.org/index.php/Cross-site_Scripting_(XSS))
36 | * [XSS (Cross Site Scripting) Prevention Cheat Sheet - OWASP](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet)
37 | * [What is Cross-site Scripting and How Can You Fix it?](https://www.acunetix.com/websitesecurity/cross-site-scripting/)
38 | * [An Introduction to Content Security Policy - HTML5 Rocks](http://www.html5rocks.com/en/tutorials/security/content-security-policy/)
39 | * [Why is the same origin policy so important? - Information Security Stack Exchange](https://security.stackexchange.com/questions/8264/why-is-the-same-origin-policy-so-important)
40 |
41 | *This report was generated by an XSS Hunter server: https://github.com/mandatoryprogrammer/xsshunter*
42 |
--------------------------------------------------------------------------------
/api/templates/pgp_encrypted_template.txt:
--------------------------------------------------------------------------------
1 | URL:
2 | {{uri}}
3 |
4 | =================================
5 | Cookies:
6 | {{cookies}}
7 |
8 | =================================
9 | Referrer:
10 | {{referrer}}
11 |
12 | =================================
13 | User-Agent:
14 | {{user-agent}}
15 |
16 | =================================
17 | Browser Time:
18 | {{browser-time}}
19 |
20 | =================================
21 | Probe UUID:
22 | {{probe-uid}}
23 |
24 | =================================
25 | Execution Origin:
26 | {{origin}}
27 |
28 | =================================
29 | Injection Key:
30 | {{injection_key}}
31 |
32 | =================================
33 | DOM:
34 | {{dom}}
35 |
36 | =================================
37 | Screenshot (in data: URI format, paste into browser to view):
38 | {{screenshot}}
39 |
--------------------------------------------------------------------------------
/api/templates/xss_email_template.htm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | XSS Hunter Report
10 | This report has been generated by an XSS Hunter server and contains the details of a cross-site scripting vulnerability. The tracking ID for this report is {{ injection_data['id'] }}
, the triggering browser reports the time of execution to be {{ injection_data['browser_time'] }}.
11 |
12 |
13 |
14 |
15 |
Vulnerable Page URL
16 |
17 |
20 |
21 |
22 |
23 |
24 |
User IP Address
25 |
26 |
29 |
30 |
31 |
32 |
33 |
Referer
34 |
35 |
38 |
39 |
40 |
41 |
42 |
User-Agent
43 |
44 |
45 | {{ injection_data['user_agent'] }}
46 |
47 |
48 |
49 |
50 |
51 |
Cookies
52 |
53 |
54 | {{ injection_data['cookies'] }}
55 |
56 |
57 |
58 |
59 |
60 |
Injection Point (Raw HTTP Request)
61 |
62 |
63 |
{{ injection_data['correlated_request'] }}
64 |
65 |
66 |
67 |
68 |
69 |
DOM
70 |
71 |
72 |
{{ injection_data['dom'] }}
73 |
74 |
75 |
76 |
77 |
78 |
Execution Origin
79 |
80 |
81 |
{{ injection_data['origin'] }}
82 |
83 |
84 |
85 | A screenshot of the affected page has been included for further investigation.
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/generate_config.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import binascii
3 | import yaml
4 | import os
5 |
6 | nginx_template = """
7 | server {
8 | # Redirect HTTP to www
9 | listen 80;
10 | server_name fakedomain.com;
11 | location / {
12 | rewrite ^/(.*)$ https://www.fakedomain.com/$1 permanent;
13 | }
14 | }
15 |
16 | server {
17 | # Redirect payloads to HTTPS
18 | listen 80;
19 | server_name *.fakedomain.com;
20 | proxy_set_header X-Forwarded-For $remote_addr;
21 |
22 | return 307 https://$host$request_uri;
23 | client_max_body_size 500M; # In case we have an extra large payload capture
24 | }
25 |
26 | server {
27 | # Redirect HTTPS to www
28 | listen 443;
29 | ssl on;
30 | ssl_certificate /etc/nginx/ssl/fakedomain.com.crt; # Wildcard SSL certificate
31 | ssl_certificate_key /etc/nginx/ssl/fakedomain.com.key; # Wildcard SSL certificate key
32 |
33 | server_name fakedomain.com;
34 | location / {
35 | rewrite ^/(.*)$ https://www.fakedomain.com/$1 permanent;
36 | }
37 | }
38 |
39 | server {
40 | # API proxy
41 | listen 443;
42 | ssl on;
43 | ssl_certificate /etc/nginx/ssl/fakedomain.com.crt; # Wildcard SSL certificate
44 | ssl_certificate_key /etc/nginx/ssl/fakedomain.com.key; # Wildcard SSL certificate key
45 |
46 | server_name *.fakedomain.com;
47 | access_log /var/log/nginx/fakedomain.com.vhost.access.log;
48 | error_log /var/log/nginx/fakedomain.com.vhost.error.log;
49 |
50 | client_max_body_size 500M;
51 |
52 | location / {
53 | proxy_pass http://localhost:8888;
54 | proxy_set_header Host $host;
55 | proxy_set_header X-Forwarded-For $remote_addr;
56 | }
57 | }
58 |
59 | server {
60 | # Redirect api to HTTPS
61 | listen 80;
62 | server_name api.fakedomain.com; # Subdomain for API server
63 | proxy_set_header X-Forwarded-For $remote_addr;
64 |
65 | return 307 https://api.fakedomain.com$request_uri;
66 | client_max_body_size 500M; # In case we have an extra large payload capture
67 | }
68 |
69 | server {
70 | # Redirect www to HTTPS
71 | listen 80;
72 | server_name www.fakedomain.com;
73 | location / {
74 | rewrite ^/(.*)$ https://www.fakedomain.com/$1 permanent;
75 | }
76 | }
77 |
78 | server {
79 | # GUI proxy
80 | listen 443;
81 | server_name www.fakedomain.com;
82 | client_max_body_size 500M;
83 | ssl on;
84 | ssl_certificate /etc/nginx/ssl/fakedomain.com.crt; # Wildcard SSL certificate
85 | ssl_certificate_key /etc/nginx/ssl/fakedomain.com.key; # Wildcard SSL certificate key
86 |
87 |
88 | location / {
89 | proxy_pass http://localhost:1234;
90 | proxy_set_header Host $host;
91 | }
92 | }
93 | """
94 |
95 | settings = {
96 | "email_from":"",
97 | "mailgun_api_key":"",
98 | "mailgun_sending_domain":"",
99 | "domain": "",
100 | "abuse_email": "",
101 | "cookie_secret": "",
102 | }
103 |
104 | print """
105 | __ __ _____ _____ _ _ _
106 | \ \ / // ____/ ____| | | | | | |
107 | \ V /| (___| (___ | |__| |_ _ _ __ | |_ ___ _ __
108 | > < \___ \\\\___ \ | __ | | | | '_ \| __/ _ \ '__|
109 | / . \ ____) |___) | | | | | |_| | | | | || __/ |
110 | /_/ \_\_____/_____/ |_| |_|\__,_|_| |_|\__\___|_|
111 |
112 |
113 | Setup Utility
114 | """
115 |
116 | print "What is the base domain name you will be using? "
117 | print "(ex. localhost, www.example.com)"
118 | hostname = raw_input( "Domain? ")
119 | if hostname != "":
120 | settings["domain"] = hostname
121 | nginx_template = nginx_template.replace( "fakedomain.com", settings["domain"] )
122 |
123 | print "Great! Now let's setup your Mailgun account to send XSS alerts to."
124 | print ""
125 | print "Enter your API key: "
126 | print "(ex. key-8da843ff65205a61374b09b81ed0fa35)"
127 | settings["mailgun_api_key"] = raw_input( "Mailgun API key: ")
128 | print ""
129 | print "What is your Mailgun domain? "
130 | print "(ex. example.com)"
131 | settings["mailgun_sending_domain"] = raw_input( "Mailgun domain: ")
132 | print ""
133 | print "What email address is sending the payload fire emails?: "
134 | print "(ex. no-reply@example.com)"
135 | settings["email_from"] = raw_input( "Sending email address: ")
136 | print ""
137 | print "Where should abuse/contact emails go?: "
138 | print "(ex. yourpersonal@gmail.com)"
139 | settings["abuse_email"] = raw_input( "Abuse/Contact email: ")
140 | print ""
141 | print ""
142 | print "What postgres user is this service using? "
143 | print "(ex. xsshunter)"
144 | settings["postgreql_username"] = raw_input( "Postgres username: ")
145 | print ""
146 | print "What is the postgres user's password? "
147 | print "(ex. @!$%@^%UOFGJOEJG$)"
148 | settings["postgreql_password"] = raw_input( "Postgres password: ")
149 | print ""
150 | print "What is the postgres user's DB? "
151 | print "(ex. xsshunter)"
152 | settings["postgres_db"] = raw_input( "Postgres DB: ")
153 | print ""
154 | print "Generating cookie secret..."
155 | settings["cookie_secret"] = binascii.hexlify( os.urandom(50) )
156 |
157 | yaml_config = yaml.dump( settings, default_flow_style=False)
158 | file_handler = open( "config.yaml", "w" )
159 | file_handler.write( yaml_config )
160 | file_handler.close()
161 |
162 | print "Minting new nginx configuration file..."
163 | file_handler = open( "default", "w" )
164 | file_handler.write( nginx_template )
165 | file_handler.close()
166 |
167 | print """
168 | Setup complete! Please now copy the 'default' file to /etc/nginx/sites-enabled/default
169 | This can be done by running the following:
170 | sudo cp default /etc/nginx/sites-enabled/default
171 |
172 | Also, please ensure your wildcard SSL certificate and key are available at the following locations:
173 | /etc/nginx/ssl/""" + hostname + """.crt; # Wildcard SSL certificate
174 | /etc/nginx/ssl/""" + hostname + """.key; # Wildcard SSL key
175 |
176 | Good luck hunting for XSS!
177 | -mandatory
178 | """
179 |
--------------------------------------------------------------------------------
/gui/guiserver.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import tornado.ioloop
3 | import tornado.web
4 | import tornado.template
5 | import dns.resolver
6 | import yaml
7 |
8 | try:
9 | with open( '../config.yaml', 'r' ) as f:
10 | settings = yaml.safe_load( f )
11 | except IOError:
12 | print "Error reading config.yaml, have you created one? (Hint: Try running ./generate_config.py)"
13 | exit()
14 |
15 | class BaseHandler(tornado.web.RequestHandler):
16 | def __init__(self, *args, **kwargs):
17 | super(BaseHandler, self).__init__(*args, **kwargs)
18 | self.set_header("X-Frame-Options", "deny")
19 | self.set_header("X-XSS-Protection", "1; mode=block")
20 | self.set_header("X-Content-Type-Options", "nosniff")
21 | self.set_header("Server", "")
22 | self.set_header("Content-Security-Policy", "default-src 'self' " + DOMAIN + " api." + DOMAIN + "; style-src 'self' fonts.googleapis.com; img-src 'self' api." + DOMAIN + "; font-src 'self' fonts.googleapis.com fonts.gstatic.com; script-src 'self'; frame-src 'self'")
23 |
24 | def compute_etag( self ):
25 | return None
26 |
27 | class XSSHunterApplicationHandler(BaseHandler):
28 | def get(self):
29 | loader = tornado.template.Loader( "templates/" )
30 | self.write( loader.load( "mainapp.htm" ).generate( domain=DOMAIN ) )
31 |
32 | class DebugOverrideStaticCaching(tornado.web.StaticFileHandler):
33 | def set_extra_headers(self, path):
34 | self.set_header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
35 |
36 | class HomepageHandler(BaseHandler):
37 | def get(self):
38 | loader = tornado.template.Loader( "templates/" )
39 | self.write( loader.load( "homepage.htm" ).generate() )
40 |
41 | class FeaturesHandler(BaseHandler):
42 | def get(self):
43 | loader = tornado.template.Loader( "templates/" )
44 | self.write( loader.load( "features.htm" ).generate( domain=DOMAIN ) )
45 |
46 | class SignUpHandler(BaseHandler):
47 | def get(self):
48 | loader = tornado.template.Loader( "templates/" )
49 | self.write( loader.load( "signup.htm" ).generate( domain=DOMAIN ) )
50 |
51 | class ContactHandler(BaseHandler):
52 | def get(self):
53 | loader = tornado.template.Loader( "templates/" )
54 | self.write( loader.load( "contact.htm" ).generate() )
55 |
56 | def make_app():
57 | return tornado.web.Application([
58 | (r"/", HomepageHandler),
59 | (r"/app", XSSHunterApplicationHandler),
60 | (r"/features", FeaturesHandler),
61 | (r"/signup", SignUpHandler),
62 | (r"/contact", ContactHandler),
63 | (r"/static/(.*)", tornado.web.StaticFileHandler, {"path": "static/"}),
64 | ])
65 |
66 | if __name__ == "__main__":
67 | DOMAIN = settings["domain"]
68 | API_SERVER = "https://api." + DOMAIN
69 | app = make_app()
70 | app.listen( 1234 )
71 | tornado.ioloop.IOLoop.current().start()
72 |
--------------------------------------------------------------------------------
/gui/requirements.txt:
--------------------------------------------------------------------------------
1 | PyYAML==3.11
2 | argparse==1.2.1
3 | backports-abc==0.4
4 | backports.ssl-match-hostname==3.5.0.1
5 | certifi==2016.2.28
6 | dnspython==1.14.0
7 | singledispatch==3.4.0.3
8 | six==1.10.0
9 | tornado==4.3
10 | wsgiref==0.1.2
11 |
--------------------------------------------------------------------------------
/gui/static/angularicons/angularicons.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/angularicons/angularicons.eot
--------------------------------------------------------------------------------
/gui/static/angularicons/angularicons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/angularicons/angularicons.ttf
--------------------------------------------------------------------------------
/gui/static/angularicons/angularicons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/angularicons/angularicons.woff
--------------------------------------------------------------------------------
/gui/static/css/aboutus.css:
--------------------------------------------------------------------------------
1 | .about_us_title {
2 | font-weight: bold;
3 | font-size: 36px;
4 | }
5 |
6 | .section_header {
7 | font-weight: bold;
8 | font-size: 24px;
9 | }
10 |
11 | .section_body_text {
12 | font-size: 20px;
13 | }
14 |
--------------------------------------------------------------------------------
/gui/static/css/app.css:
--------------------------------------------------------------------------------
1 | input, textarea {
2 | margin-top: 5px;
3 | margin-bottom: 10px;
4 | display: inline-block;
5 | *display: inline; /* for IE7*/
6 | zoom:1; /* for IE7*/
7 | }
8 |
9 | label {
10 | display:inline-block;
11 | *display: inline; /* for IE7*/
12 | zoom: 1; /* for IE7*/
13 | float: left;
14 | text-align: right;
15 | }
16 |
--------------------------------------------------------------------------------
/gui/static/css/contactus.css:
--------------------------------------------------------------------------------
1 | input, textarea {
2 | margin-top: 5px;
3 | margin-bottom: 10px;
4 | display: inline-block;
5 | *display: inline; /* for IE7*/
6 | zoom:1; /* for IE7*/
7 | }
8 |
9 | label {
10 | display:inline-block;
11 | *display: inline; /* for IE7*/
12 | zoom: 1; /* for IE7*/
13 | float: left;
14 | text-align: right;
15 | }
16 |
17 | #name {
18 | max-width: 300px;
19 | }
20 |
21 | #body{
22 | max-width: 700px;
23 | max-height: 100px;
24 | }
25 |
26 | .contact-us-form {
27 | max-width: 700px;
28 | top: 0;
29 | bottom: 0;
30 | left: 0;
31 | right: 0;
32 | margin: auto;
33 | }
34 |
35 | .contact_us_form_success_message {
36 | display: none;
37 | text-align: center;
38 | }
39 |
--------------------------------------------------------------------------------
/gui/static/css/cover.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Globals
3 | */
4 |
5 | /* Links */
6 | a,
7 | a:focus,
8 | a:hover {
9 | color: #fff;
10 | }
11 |
12 | /* Custom default button */
13 | .btn-default,
14 | .btn-default:hover,
15 | .btn-default:focus {
16 | color: #333;
17 | text-shadow: none; /* Prevent inheritence from `body` */
18 | background-color: #fff;
19 | border: 1px solid #fff;
20 | }
21 |
22 |
23 | /*
24 | * Base structure
25 | */
26 |
27 | html,
28 | body {
29 | height: 100%;
30 | background-color: #333;
31 | }
32 | body {
33 | color: #fff;
34 | text-align: center;
35 | text-shadow: 0 1px 3px rgba(0,0,0,.5);
36 | }
37 |
38 | /* Extra markup and styles for table-esque vertical and horizontal centering */
39 | .site-wrapper {
40 | display: table;
41 | width: 100%;
42 | height: 100%; /* For at least Firefox */
43 | min-height: 100%;
44 | -webkit-box-shadow: inset 0 0 100px rgba(0,0,0,.5);
45 | box-shadow: inset 0 0 100px rgba(0,0,0,.5);
46 | }
47 | .site-wrapper-inner {
48 | display: table-cell;
49 | vertical-align: top;
50 | }
51 | .cover-container {
52 | margin-right: auto;
53 | margin-left: auto;
54 | }
55 |
56 | /* Padding for spacing */
57 | .inner {
58 | padding: 30px;
59 | }
60 |
61 |
62 | /*
63 | * Header
64 | */
65 | .masthead-brand {
66 | margin-top: 10px;
67 | margin-bottom: 10px;
68 | }
69 |
70 | .masthead-nav > li {
71 | display: inline-block;
72 | }
73 | .masthead-nav > li + li {
74 | margin-left: 20px;
75 | }
76 | .masthead-nav > li > a {
77 | padding-right: 0;
78 | padding-left: 0;
79 | font-size: 16px;
80 | font-weight: bold;
81 | color: #fff; /* IE8 proofing */
82 | color: rgba(255,255,255,.75);
83 | border-bottom: 2px solid transparent;
84 | }
85 | .masthead-nav > li > a:hover,
86 | .masthead-nav > li > a:focus {
87 | background-color: transparent;
88 | border-bottom-color: #a9a9a9;
89 | border-bottom-color: rgba(255,255,255,.25);
90 | }
91 | .masthead-nav > .active > a,
92 | .masthead-nav > .active > a:hover,
93 | .masthead-nav > .active > a:focus {
94 | color: #fff;
95 | border-bottom-color: #fff;
96 | }
97 |
98 | @media (min-width: 768px) {
99 | .masthead-brand {
100 | float: left;
101 | }
102 | .masthead-nav {
103 | float: right;
104 | }
105 | }
106 |
107 |
108 | /*
109 | * Cover
110 | */
111 |
112 | .cover {
113 | padding: 0 20px;
114 | }
115 | .cover .btn-lg {
116 | padding: 10px 20px;
117 | font-weight: bold;
118 | }
119 |
120 |
121 | /*
122 | * Footer
123 | */
124 |
125 | .mastfoot {
126 | color: #999; /* IE8 proofing */
127 | color: rgba(255,255,255,.5);
128 | }
129 |
130 |
131 | /*
132 | * Affix and center
133 | */
134 |
135 | @media (min-width: 768px) {
136 | /* Pull out the header and footer */
137 | .masthead {
138 | position: fixed;
139 | top: 0;
140 | }
141 | .mastfoot {
142 | position: fixed;
143 | bottom: 0;
144 | }
145 | /* Start the vertical centering */
146 | .site-wrapper-inner {
147 | vertical-align: middle;
148 | }
149 | /* Handle the widths */
150 | .masthead,
151 | .mastfoot,
152 | .cover-container {
153 | width: 100%; /* Must be percentage or pixels for horizontal alignment */
154 | }
155 | }
156 |
157 | @media (min-width: 992px) {
158 | .masthead,
159 | .mastfoot,
160 | .cover-container {
161 | width: 700px;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/gui/static/css/docs.css:
--------------------------------------------------------------------------------
1 | .docs_title_section {
2 | font-size: 36px;
3 | }
4 |
5 | .docs_paragraph {
6 | padding-bottom: 20px;
7 | font-size: 20px;
8 | }
9 |
10 | .docs_image {
11 | max-width: 100%;
12 | margin-bottom: 20px;
13 | }
14 |
--------------------------------------------------------------------------------
/gui/static/css/features.css:
--------------------------------------------------------------------------------
1 | .features_title_section {
2 | font-size: 36px;
3 | }
4 |
5 | .features_paragraph {
6 | padding-bottom: 20px;
7 | font-size: 20px;
8 | }
9 |
10 | .features_image {
11 | max-width: 100%;
12 | margin-bottom: 20px;
13 | }
14 |
--------------------------------------------------------------------------------
/gui/static/css/homepage.css:
--------------------------------------------------------------------------------
1 | #homepage_jumbotron {
2 | max-width: 1200px;
3 | top: 0;
4 | bottom: 0;
5 | left: 0;
6 | right: 0;
7 | margin: auto;
8 | }
9 |
10 | #autoplay_homepage_video {
11 | width: 100%;
12 | max-height: 600px;
13 | }
14 |
15 | .jumbotron-photo {
16 | background-color: #000000;
17 | }
18 |
19 | .if_this_is_how_text {
20 | padding: 20px;
21 | }
22 |
--------------------------------------------------------------------------------
/gui/static/css/main.css:
--------------------------------------------------------------------------------
1 | .menu {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | background-image: url("/static/img/bg.png");
7 | background-repeat: repeat;
8 | padding-top: 50px;
9 | }
10 |
11 | .main-content {
12 | margin-top: 40px;
13 | text-align: center;
14 | }
15 |
16 | .loading_bar {
17 | width: 100%;
18 | }
19 |
20 | .progress-bar {
21 | width: 100%;
22 | }
23 |
24 | .beta_xss_hunter_logo {
25 | color: #ff0000;
26 | }
27 | .panel-body {
28 | margin-bottom: 20px;
29 | }
30 |
--------------------------------------------------------------------------------
/gui/static/css/mainapp.css:
--------------------------------------------------------------------------------
1 | .xsshunter_application {
2 | display: none;
3 | }
4 |
5 | .reset-password-form {
6 | display: none;
7 | }
8 |
9 | input, textarea {
10 | margin-top: 5px;
11 | margin-bottom: 10px;
12 | display: inline-block;
13 | *display: inline; /* for IE7*/
14 | zoom:1; /* for IE7*/
15 | }
16 |
17 | label {
18 | display:inline-block;
19 | *display: inline;
20 | zoom: 1;
21 | text-align: right;
22 | }
23 |
24 | #username {
25 | max-width: 300px;
26 | }
27 |
28 | #password {
29 | max-width: 300px;
30 | }
31 |
32 | .login_form {
33 | max-width: 330px;
34 | top: 0;
35 | bottom: 0;
36 | left: 0;
37 | right: 0;
38 | margin: auto;
39 | }
40 |
41 | .updated_settings_success_dialogue {
42 | display: none;
43 | text-align: center;
44 | }
45 |
46 | .bad_password_dialogue {
47 | display: none;
48 | text-align: center;
49 | }
50 |
51 | .xss_fire_thumbnail_column {
52 | max-width: 400px;
53 | }
54 |
55 | .victim_ip_address_column {
56 | max-width: 140px;
57 | }
58 |
59 | .xss_payload_fire_options_column_header {
60 | text-align: center;
61 | }
62 |
63 | .login-in-form {
64 | display: none;
65 | }
66 |
67 | .bad_account_update_dialogue {
68 | display: none;
69 | }
70 |
71 | .invalid_fields {
72 | display: inline;
73 | }
74 |
75 | .paginator_div {
76 | margin: 20px;
77 | }
78 |
79 | .full_injection_report_container {
80 | margin: 40px;
81 | background-color: #434a54;
82 | }
83 |
84 | .collected_page_full_page_view {
85 | margin: 40px;
86 | padding: 8px;
87 | background-color: #434a54;
88 | }
89 |
90 | .pagination {
91 | padding-left: 20px;
92 | }
93 |
94 | .full_report_screenshot {
95 | width: 100%;
96 | }
97 |
98 | .injection_full_report_top_panel {
99 | margin-top: 50px;
100 | }
101 |
102 | .injection_html_dom {
103 | display: inline-block;
104 | overflow: auto;
105 | float: left;
106 | max-height: 400px;
107 | white-space: pre-wrap;
108 | word-wrap: normal;
109 | }
110 |
111 | .injection_markdown_report {
112 | display: inline-block;
113 | overflow: auto;
114 | float: left;
115 | max-height: 400px;
116 | white-space: pre-wrap;
117 | word-wrap: normal;
118 | }
119 |
120 | li.L0, li.L1, li.L2, li.L3,
121 | li.L5, li.L6, li.L7, li.L8
122 | {
123 | list-style-type: decimal !important
124 | }
125 |
126 | .email_enabled_toggle {
127 | float: none !important;
128 | }
129 |
130 | .logout_button {
131 | }
132 |
133 | .collected_pages_options_button_td {
134 | width: 200px;
135 | }
136 |
137 | #injection_data_table {
138 | table-layout:fixed;
139 | width: 100%;
140 | }
141 |
142 | .vulnerable_page_uri_column {
143 | max-width: 0;
144 | overflow: hidden;
145 | text-overflow: ellipsis;
146 | white-space: nowrap;
147 | }
148 |
149 | .victim_ip_address_column {
150 | max-width: 0;
151 | overflow: hidden;
152 | text-overflow: ellipsis;
153 | white-space: nowrap;
154 | }
155 |
156 | .xss_fire_thumbnail_image {
157 | width: 100%;
158 | max-height: 400px;
159 | cursor: pointer;
160 | }
161 |
162 | .password_reset_success_dialogue {
163 | display: none;
164 | }
165 |
--------------------------------------------------------------------------------
/gui/static/css/prettify.css:
--------------------------------------------------------------------------------
1 | /* Pretty printing styles. Used with prettify.js. */
2 |
3 | /* SPAN elements with the classes below are added by prettyprint. */
4 | .pln { color: #000 } /* plain text */
5 |
6 | @media screen {
7 | .str { color: #080 } /* string content */
8 | .kwd { color: #008 } /* a keyword */
9 | .com { color: #800 } /* a comment */
10 | .typ { color: #606 } /* a type name */
11 | .lit { color: #066 } /* a literal value */
12 | /* punctuation, lisp open bracket, lisp close bracket */
13 | .pun, .opn, .clo { color: #660 }
14 | .tag { color: #008 } /* a markup tag name */
15 | .atn { color: #606 } /* a markup attribute name */
16 | .atv { color: #080 } /* a markup attribute value */
17 | .dec, .var { color: #606 } /* a declaration; a variable name */
18 | .fun { color: red } /* a function name */
19 | }
20 |
21 | /* Use higher contrast and text-weight for printable form. */
22 | @media print, projection {
23 | .str { color: #060 }
24 | .kwd { color: #006; font-weight: bold }
25 | .com { color: #600; font-style: italic }
26 | .typ { color: #404; font-weight: bold }
27 | .lit { color: #044 }
28 | .pun, .opn, .clo { color: #440 }
29 | .tag { color: #006; font-weight: bold }
30 | .atn { color: #404 }
31 | .atv { color: #060 }
32 | }
33 |
34 | /* Put a border around prettyprinted code snippets. */
35 | pre.prettyprint { padding: 2px; border: 1px solid #888 }
36 |
37 | /* Specify class=linenums on a pre to get line numbering */
38 | ol.linenums { margin-top: 0; margin-bottom: 0 } /* IE indents via margin-left */
39 | li.L0,
40 | li.L1,
41 | li.L2,
42 | li.L3,
43 | li.L5,
44 | li.L6,
45 | li.L7,
46 | li.L8 { list-style-type: none }
47 | /* Alternate shading for lines */
48 | li.L1,
49 | li.L3,
50 | li.L5,
51 | li.L7,
52 | li.L9 { background: #eee }
53 |
--------------------------------------------------------------------------------
/gui/static/css/pricing.css:
--------------------------------------------------------------------------------
1 | .pricing_title_section {
2 | font-size: 36px;
3 | }
4 |
--------------------------------------------------------------------------------
/gui/static/css/signup.css:
--------------------------------------------------------------------------------
1 | input, textarea {
2 | margin-top: 5px;
3 | margin-bottom: 10px;
4 | display: inline-block;
5 | *display: inline; /* for IE7*/
6 | zoom:1; /* for IE7*/
7 | }
8 |
9 | label {
10 | display:inline-block;
11 | *display: inline; /* for IE7*/
12 | zoom: 1; /* for IE7*/
13 | float: left;
14 | text-align: right;
15 | }
16 |
17 | #full_name {
18 | max-width: 400px;
19 | }
20 |
21 | #username {
22 | max-width: 300px;
23 | }
24 |
25 | #password {
26 | max-width: 300px;
27 | }
28 |
29 | #domain {
30 | max-width: 300px;
31 | }
32 |
33 | #email {
34 | max-width: 400px;
35 | }
36 |
37 | .sign-up-form {
38 | max-width: 400px;
39 | top: 0;
40 | bottom: 0;
41 | left: 0;
42 | right: 0;
43 | margin: auto;
44 | }
45 |
46 | .bad_signup_dialogue {
47 | display: none;
48 | }
49 |
50 | .asterisk {
51 | color: #ED0000;
52 | display: inline;
53 | }
54 |
--------------------------------------------------------------------------------
/gui/static/css/site.css:
--------------------------------------------------------------------------------
1 | body {
2 | position: relative; /* For scrollyspy */
3 | /*font-family: 'Lato', sans-serif !important;*/
4 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif !important;
5 | /*font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; */
6 | }
7 | h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
8 | /*font-family: 'Lato', sans-serif !important;*/
9 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif !important;
10 | }
11 | /* ----------------------------------
12 | * home
13 | * ---------------------------------- */
14 | .home {
15 | background: url(../img/index.png) no-repeat 50% 100px;
16 | }
17 | @media (max-width: 767px) {
18 | .home {
19 | background: none;
20 | }
21 | }
22 | /* ----------------------------------
23 | * icons
24 | * ---------------------------------- */
25 | @font-face {
26 | font-family: "angularicons";
27 | src: url("../angularicons/angularicons.eot");
28 | src: url("../angularicons/angularicons.eot?#iefix") format("embedded-opentype"), url("../angularicons/angularicons.woff") format("woff"), url("../angularicons/angularicons.ttf") format("truetype"), url("../angularicons/angularicons.svg") format("svg");
29 | }
30 | .icon {
31 | display: inline-block;
32 | font-family: "angularicons";
33 | font-weight: normal;
34 | font-style: normal;
35 | text-decoration: none;
36 | vertical-align: top;
37 | -webkit-font-smoothing: antialiased;
38 | -moz-osx-font-smoothing: grayscale;
39 | }
40 | .icon[data-icon]:before {
41 | content: attr(data-icon);
42 | speak: none;
43 | }
44 | /* ----------------------------------
45 | * index
46 | * ---------------------------------- */
47 | .index {
48 | text-align: center;
49 | background-color: rgba(255, 255, 255, .9);
50 | min-height: 746px;
51 | }
52 | .index h2 {
53 | margin-top: 10px;
54 | color: rgba(0, 0, 0, .8);
55 | font-size: 16px;
56 | line-height: 26px;
57 | font-weight: normal;
58 | }
59 | .index h3 {
60 | margin-top: 20px;
61 | font-size: 14px;
62 | font-style: italic;
63 | }
64 | .index .download-link {
65 | margin: 20px 0;
66 | }
67 | .index .download-link .btn {
68 | padding: 15px 35px;
69 | border: 0;
70 | font-size: 22px;
71 | opacity: 1;
72 | }
73 | .index .version-text {
74 | color: rgba(0, 0, 0, .6);
75 | font-size: 12px;
76 | }
77 | .index .learn-more {
78 | margin-top: 20px;
79 | font-weight: bold;
80 | color: rgba(79, 190, 186, 1);
81 | }
82 | .index .learn-more a,
83 | .index .learn-more a:hover,
84 | .index .learn-more a:active {
85 | color: #50c1e9 !important;
86 | text-decoration: none;
87 | }
88 | .index .learn-more .icon {
89 | font-weight: bold;
90 | font-size: 16px;
91 | }
92 | .index .adpacks {
93 | margin: 0 auto 100px;
94 | padding: 9px 9px 18px;
95 | border-radius: 3px;
96 | border: 1px solid #3bafda;
97 | width: 380px;
98 | min-height: 132px;
99 | text-align: left !important;
100 | }
101 | .index .adpacks .adpacks-img {
102 | float: left;
103 | width: 140px;
104 | }
105 |
106 | .index .adpacks .adpacks-text,
107 | .index .adpacks .adpacks-poweredby {
108 | font-size: 13px;
109 | line-height: 16px;
110 | }
111 | .index .adpacks .adpacks-poweredby {
112 | display: block;
113 | margin-bottom:9px;
114 | }
115 | @media (max-width: 767px) {
116 | .index {
117 | min-height: 600px;
118 | }
119 | .index .download-link .btn {
120 | padding: 15px;
121 | }
122 |
123 | .index .adpacks {
124 | width: 100%;
125 | min-height: 132px;
126 | }
127 | }
128 | /* ----------------------------------
129 | * social
130 | * ---------------------------------- */
131 | .social {
132 | position: relative;
133 | margin-top: -100px;
134 | margin-bottom: 0;
135 | padding: 40px 5px;
136 | height: 99px;
137 | text-align: center;
138 | background: rgba(255, 255, 255, .7) url(../img/divider.png) no-repeat 50% 100%;
139 | }
140 | .social li {
141 | height: 30px;
142 | display: inline-block;
143 | vertical-align: top;
144 | }
145 | .social a {
146 | color: #50c1e9 !important;
147 | }
148 | .social .github-watch {
149 | width: 105px;
150 | }
151 | .social .github-fork {
152 | width: 105px;
153 | }
154 | .social .twitter-share {
155 | width: 80px;
156 | margin-right: 10px;
157 | }
158 | .social .twitter-follow {
159 | width: 200px;
160 | }
161 | /* ----------------------------------
162 | * desc
163 | * ---------------------------------- */
164 | .desc {
165 | text-align: center;
166 | background-color: rgba(255, 255, 255, 1);
167 | }
168 | .desc a {
169 | color: rgba(79, 190, 186, 1);
170 | }
171 | .desc a:hover,
172 | .desc a:active {
173 | color: #50c1e9;
174 | text-decoration: none;
175 | }
176 | .desc .desc__introduces {
177 | border-top: 1px dashed #AAB2BD ;
178 | }
179 | .desc .desc__introduces:first-child {
180 | border-top: none;
181 | }
182 | .desc .desc__introduces h3,
183 | .desc .desc__introduces p {
184 | margin: 0 auto;
185 | }
186 | .desc .desc__introduces h3 {
187 | padding: 70px 0 14px;
188 | max-width: 900px;
189 | font-size: 28px;
190 | }
191 | .desc .desc__introduces p {
192 | padding-bottom: 40px;
193 | max-width: 900px;
194 | font-size: 18px;
195 | color: #888;
196 | }
197 | .desc .desc__introduces .photo--responsive img {
198 | width: 100%;
199 | }
200 | .desc .desc__features {
201 | margin: 70px auto;
202 | text-align: left;
203 | }
204 | .desc .desc__features .row {
205 | margin-bottom: 70px;
206 | }
207 | .desc .desc__features .col-md-6 {
208 | padding-left: 247px;
209 | min-height: 200px;
210 | text-align: left;
211 | }
212 | .desc .desc__features .features__photo {
213 | position: absolute;
214 | top: -18px;
215 | left: 0;
216 | }
217 | .desc .desc__features h4 {
218 | font-size: 20px;
219 | margin-bottom: 10px;
220 | }
221 | .desc .desc__features p {
222 | font-size: 16px;
223 | color: #888;
224 | line-height: 26px;
225 | }
226 | @media (max-width: 767px) {
227 | .desc .desc__introduces h3 {
228 | padding-top: 20px;
229 | text-align: left;
230 | }
231 | .desc .desc__introduces p {
232 | text-align: left;
233 | }
234 | .desc .desc__features {
235 | margin-top: 20px;
236 | }
237 | .desc .desc__features .row {
238 | margin-bottom: 20px;
239 | }
240 | .desc .desc__features .row p {
241 | padding-bottom: 20px;
242 | text-align: left;
243 | }
244 | .desc .desc__features .features__photo {
245 | position: static;
246 | }
247 | .desc .desc__features .col-md-6 {
248 | padding: 0 15px;
249 | text-align: center;
250 | }
251 | }
252 | /* ----------------------------------
253 | * docs
254 | * ---------------------------------- */
255 | .docs-header {
256 | padding-top: 50px;
257 | border-top: 1px solid #50c1e9;
258 | background: url(../img/wild_flowers.png) repeat 0 0;
259 | }
260 | .docs-header.header--noBackground {
261 | background: none;
262 | }
263 | @media (max-width: 768px) {
264 | .docs-header {
265 | padding-top: 0;
266 | }
267 | }
268 | /* ----------------------------------
269 | * navbar
270 | * ---------------------------------- */
271 | .navbar-custom {
272 | position: fixed;
273 | top: 0;
274 | left: 0;
275 | border: none;
276 | border-radius: 0;
277 | background-color: rgba(255, 255, 255, .9);
278 | width: 100%;
279 | z-index: 2000;
280 | }
281 | .navbar-custom .nav li a {
282 | display: block;
283 | color: #50c1e9;
284 | }
285 | .navbar-custom .nav li a:focus,
286 | .navbar-custom .nav li a:hover {
287 | color: #50c1e9;
288 | }
289 | .navbar-custom .nav li a:active,
290 | .navbar-custom .nav li a.current {
291 | border-bottom: 3px solid #50c1e9;
292 | }
293 | .navbar-custom .navbar-toggle {
294 | position: relative;
295 | background-color: #50c1e9;
296 | border-color: #50c1e9;
297 | }
298 | .navbar-custom .navbar-toggle:hover,
299 | .navbar-custom .navbar-toggle:focus {
300 | background-color: #50c1e9;
301 | }
302 | .navbar-custom .navbar-toggle .icon-bar {
303 | background-color: rgba(255, 255, 255, .9);
304 | }
305 | .navbar-custom .navbar-brand {
306 | padding: 5px 15px;
307 | opacity: .7;
308 | filter:alpha(opacity=70);
309 | transition: opacity .4s ease-in-out;
310 | }
311 | .navbar-custom .navbar-brand:hover,
312 | .navbar-custom .navbar-brand:active {
313 | opacity: 1;
314 | filter:alpha(opacity=100);
315 | }
316 | .navbar-default .navbar-collapse {
317 | border-color: #e7e7e7;
318 | }
319 | @media (max-width: 992px) {
320 | .navbar-custom .navbar-brand {
321 | width: 63px;
322 | overflow: hidden;
323 | }
324 | }
325 | @media (max-width: 767px) {
326 | .navbar-custom {
327 | position: relative;
328 | top: 0;
329 | }
330 | .navbar-custom .navbar-nav > li > a:hover,
331 | .navbar-custom .navbar-nav > li > a:focus {
332 | color: #fff;
333 | background-color: #50c1e9;
334 | }
335 | }
336 | /* ----------------------------------
337 | * topic
338 | * ---------------------------------- */
339 | .topic {
340 | position: relative;
341 | padding: 50px 0 110px;
342 | }
343 | .topic h3 {
344 | margin-top: 20px;
345 | color: #fff;
346 | font-size: 28px;
347 | font-weight: normal;
348 | }
349 | .topic h4 {
350 | margin-top: 15px;
351 | color: rgba(255, 255, 255, .8);
352 | font-weight: normal;
353 | }
354 | .topic .topic__infos {
355 | position: absolute;
356 | bottom: 0;
357 | padding-bottom: 15px;
358 | padding-top: 14px;
359 | background: rgba(255, 255, 255, 0.25);
360 | width: 100%;
361 | }
362 | .topic .container {
363 | position: relative;
364 | color: rgba(255, 255, 255, .8);
365 | }
366 | .topic .container a {
367 | color: #fff;
368 | filter:alpha(opacity=100);
369 | opacity: 1;
370 | text-decoration: underline;
371 | padding: 0;
372 | font-weight: normal;
373 | }
374 | .topic .container a.btn {
375 | padding: 10px 16px;
376 | text-decoration: none;
377 | }
378 | .topic .github {
379 | position: relative;
380 | top: 10px;
381 | }
382 | /* ----------------------------------
383 | * advertisement
384 | * ---------------------------------- */
385 | .advertisement {
386 | padding: 5px;
387 | width: auto !important;
388 | overflow: hidden; /* clearfix */
389 | text-align: left;
390 | border: 1px solid #a4e4ef !important;
391 | border-radius: 4px;
392 | }
393 | @media (max-width: 767px) {
394 | .advertisement {
395 | position: static;
396 | margin: 30px 0;
397 | }
398 | }
399 | @media (min-width: 768px) {
400 | .advertisement {
401 | position: absolute;
402 | top: 15px;
403 | right: 15px; /* 15px instead of 0 since box-sizing */
404 | width: 380px !important;
405 | }
406 | }
407 | .carbon-wrap {
408 | display:block;
409 | height:100px;
410 | line-height:15px;
411 | overflow:hidden;
412 | }
413 | .carbon-img{
414 | border:none;
415 | display:inline;
416 | float:left;
417 | height:100px;
418 | margin:4px 9px 9px 0;
419 | width:130px
420 | }
421 | .carbon-text{
422 | display:inline;
423 | float:left;
424 | width:162px;
425 | color:#000;
426 | text-decoration:none !important;
427 | text-transform:none;
428 | font-size:11px !important
429 | }
430 | .carbon-poweredby {
431 | float: right;
432 | text-align:center;
433 | color:#999
434 | text-decoration:none !important;
435 | font-size:11px !important
436 | }
437 | /* ----------------------------------
438 | * documents
439 | * ---------------------------------- */
440 | .documents {
441 | margin-top: 40px;
442 | }
443 | /* ----------------------------------
444 | * details
445 | * ---------------------------------- */
446 | .details {
447 | position: relative;
448 | padding-right: 400px;
449 | }
450 | @media (max-width: 991px) {
451 | .details { width: 100%; }
452 | }
453 | @media (max-width: 767px) {
454 | .details { padding: 0 15px; }
455 | }
456 | /* ----------------------------------
457 | * docs-article
458 | * ---------------------------------- */
459 | .docs-article {
460 | margin-bottom: 40px;
461 | padding-bottom: 40px;
462 | border-bottom: 1px solid #ddd;
463 | }
464 | .docs-article:last-child {
465 | border-bottom: none;
466 | }
467 | .docs-article h3 {
468 | margin: 0 0 10px;
469 | padding-bottom: 10px;
470 | font-family: "Georgia", "Palatino", "Times New Roman", "Times" !important;
471 | font-weight: normal;
472 | font-size: 25px;
473 | color: #50c1e9;
474 | }
475 | .docs-article p {
476 | margin-bottom: 10px;
477 | font-size: 14px;
478 | line-height: 22px;
479 | }
480 | .docs-article dd {
481 | margin-bottom: 10px;
482 | }
483 | .docs-article a {
484 | color: #50c1e9;
485 | text-decoration: underline;
486 | }
487 | .docs-article a:hover {
488 | color: #50c1e9;
489 | }
490 | .docs-article .item__infos {
491 | margin: 0 0 10px 0;
492 | font-size: 14px;
493 | list-style: disc;
494 | }
495 | .docs-article .item__infos li {
496 | margin-bottom: 10px;
497 | }
498 | .docs-article .btn-primary,
499 | .docs-article .btn-primary:hover {
500 | color: #fff;
501 | text-decoration: none;
502 | }
503 | .docs-article pre {
504 | border: none;
505 | background-color: #f7f7f7;
506 | }
507 | /* ----------------------------------
508 | * example
509 | * ---------------------------------- */
510 | .example {
511 | margin-bottom: 20px;
512 | }
513 | .example .example-title {
514 | margin-bottom: 20px;
515 | font-weight: 700;
516 | font-size: 18px;
517 | text-shadow: 2px 2px 2px rgba(255, 255, 255, 1);
518 | }
519 | .example .example-title span {
520 | font-weight: normal;
521 | font-size: 14px;
522 | color: #ED5565;
523 | }
524 | .example [class*="col-"] {
525 | margin-bottom: 10px;
526 | }
527 | .example-dropdown h2 + .dropdown-menu,
528 | .example-popover .popover,
529 | .example-modal .modal {
530 | position: static;
531 | display: block;
532 | }
533 | .example-dropdown h2 + .dropdown-menu {
534 | float: none;
535 | width: 200px;
536 | }
537 | .example-popover .popover {
538 | position: relative;
539 | }
540 | @media (max-width: 960px) {
541 | .tooltip-demo [class*="col-"] {
542 | text-align: center;
543 | }
544 | .tooltip-demo [class*="col-"] .btn-block {
545 | display: inline-block;
546 | margin: 0 auto;
547 | width: 160px;
548 | }
549 | }
550 | .example-popover [class*="col-"]:nth-of-type(1) .popover {
551 | margin-top: 0;
552 | }
553 | .example-popover [class*="col-"]:nth-of-type(3) .popover,
554 | .example-popover [class*="col-"]:nth-of-type(4) .popover {
555 | margin-top: 40px;
556 | }
557 | .example-progress [class*="col-"]:last-child .progress {
558 | margin-bottom: 10px;
559 | }
560 | .example-pagination .pagination,
561 | .example-pagination .pager {
562 | margin: 0 !important;
563 | }
564 |
565 | .example-modal .modal {
566 | overflow: hidden;
567 | }
568 | @media (min-width: 768px) {
569 | .example-modal .modal-dialog {
570 | width: 545px;
571 | margin: 5px;
572 | }
573 | }
574 | .example-typography {
575 | position: relative;
576 | padding-left: 25%;
577 | margin-bottom: 40px;
578 | }
579 | .example-typography .heading-note,
580 | .example-typography .text-note {
581 | display: block;
582 | width: 260px;
583 | position: absolute;
584 | bottom: 2px;
585 | left: 0;
586 | font-size: 13px;
587 | line-height: 13px;
588 | color: #AAB2BD;
589 | font-weight: 400;
590 | }
591 | .example-typography .text-note {
592 | bottom: auto;
593 | top: 10px;
594 | }
595 | /* ----------------------------------
596 | * color-swatches
597 | * ---------------------------------- */
598 | .color-swatches .swatches {
599 | -webkit-border-radius: 4px;
600 | -moz-border-radius: 4px;
601 | border-radius: 4px;
602 | background-color: #FFF;
603 | width: 100%;
604 | box-shadow: 0 1px 2px rgba(0, 0, 0, .2);
605 | }
606 | .color-swatches .light,
607 | .color-swatches .dark {
608 | width: 50%;
609 | height: 50px;
610 | }
611 | .color-swatches .light {
612 | -webkit-border-radius: 4px 0 0 0;
613 | -moz-border-radius: 4px 0 0 0;
614 | border-radius: 4px 0 0 0;
615 | }
616 | .color-swatches .dark {
617 | -webkit-border-radius: 0 4px 0 0;
618 | -moz-border-radius: 0 4px 0 0;
619 | border-radius: 0 4px 0 0;
620 | }
621 | .color-swatches .infos {
622 | padding: 5px 10px;
623 | }
624 | .color-swatches .infos h4,
625 | .color-swatches .infos p {
626 | margin: 0;
627 | }
628 | .color-swatches .infos h4 {
629 | margin-bottom: 3px;
630 | font-weight: bold;
631 | font-size: 14px;
632 | }
633 | .color-swatches .infos p {
634 | font-size: 12px;
635 | }
636 | /* ----------------------------------
637 | * psd-download
638 | * ---------------------------------- */
639 | .psd-download {
640 | padding: 40px 0;
641 | background: #e6e9ed url(../img/github.png) no-repeat 100% 0;
642 | min-height: 680px;
643 | }
644 | .psd-download h2 {
645 | text-align: center;
646 | }
647 | .psd-download .infos {
648 | font-size: 20px;
649 | line-height: 40px;
650 | }
651 | .psd-download .row {
652 | margin-top: 20px;
653 | }
654 | @media (max-width: 768px) {
655 | .psd-download [class*="col-md-"] {
656 | text-align: center;
657 | }
658 | }
659 | .psd-download h4 {
660 | margin: 0 0 15px 0;
661 | }
662 | .psd-download img {
663 | margin-bottom: 15px;
664 | }
665 | /* ----------------------------------
666 | * previews
667 | * ---------------------------------- */
668 | .previews {
669 | text-align: center;
670 | }
671 | .previews p {
672 | margin-bottom: 20px;
673 | font-size: 20px;
674 | line-height: 40px;
675 | }
676 | .previews img {
677 | max-width: 100%;
678 | }
679 | /* ----------------------------------
680 | * color picker
681 | * ---------------------------------- */
682 | .color-picker-nav a {
683 | text-decoration: none !important;
684 | color: #0b859c !important;
685 | margin: 0 10px;
686 | }
687 | .color-picker-nav a:hover {
688 | text-decoration: underline !important;
689 | }
690 | .color-picker-nav a.current {
691 | font-weight: bold;
692 | }
693 | .color-wrap {
694 | position: relative;
695 | clear: both;
696 | left: 0;
697 | width: 100%;
698 | z-index: 500;
699 | }
700 | .color-picker {
701 | width: 20%;
702 | padding-bottom: 18%;
703 | color: #FFF;
704 | position: relative;
705 | float: left;
706 | top: 0;
707 | bottom: 0;
708 | }
709 | .color-item {
710 | position: absolute;
711 | margin: 0;
712 | left: 0;
713 | right: 0;
714 | top: 0;
715 | bottom: 0;
716 | text-align: center;
717 | text-transform: uppercase;
718 | padding: 35%;
719 | }
720 | .color-item:hover {
721 | display: inline;
722 | z-index: 999;
723 | overflow: hidden;
724 | cursor: pointer;
725 | -webkit-transform: scale(1);
726 | -moz-transform: scale(1);
727 | -o-transform: scale(1);
728 | transform: scale(1);
729 | -webkit-box-shadow: 5px 5px 5px 5px rgba(0, 0, 0, 0.2);
730 | -moz-box-shadow: 5px 5px 5px 5px rgba(0, 0, 0, 0.2);
731 | box-shadow: 5px 5px 5px 5px rgba(0, 0, 0, 0.2);
732 | }
733 | /* Tablet Portrait size to standard 960 (devices and browsers) */
734 | @media only screen and (min-width: 768px) and (max-width: 959px)
735 | {
736 | .color-picker{
737 | width: 50%;
738 | color: #FFF;
739 | }
740 | .color-item{
741 | padding: 8%;
742 | font-size: 0.875em;
743 | }
744 | .color-name,.hex-number {
745 | position: relative;
746 | line-height: 22px;
747 | text-align: center;
748 | }
749 | }
750 |
751 | /* All Mobile Sizes (devices and browser) */
752 | @media only screen and (max-width: 767px)
753 | {
754 | .color-picker {
755 | width: 50%;
756 | color: #FFF;
757 | }
758 | .color-item{
759 | padding: 8%;
760 | font-size: 0.750em;
761 | }
762 | .color-name,.hex-number {
763 | position: relative;
764 | line-height: 22px;
765 | text-align: center;
766 | }
767 | }
768 |
769 | /* Mobile Landscape Size to Tablet Portrait (devices and browsers) */
770 | @media only screen and (min-width: 480px) and (max-width: 767px)
771 | {
772 | .color-item{
773 | padding: 5%;
774 | font-size: 0.750em;
775 | }
776 | .color-picker{
777 | width: 100%;
778 | color: #FFF;
779 | }
780 | .color-name,.hex-number {
781 | position: relative;
782 | line-height: 12px;
783 | text-align: center;
784 | }
785 | }
786 |
787 | /* ----------------------------------
788 | * btn-group & labels
789 | * ---------------------------------- */
790 | .blank {
791 | display: none;
792 | }
793 | @media (max-width: 768px) {
794 | .blank {
795 | display: block;
796 | height: 15px;
797 | }
798 | }
799 | .site-footer {
800 | position: relative;
801 | z-index: 400;
802 | border-top: 1px dashed #AAB2BD;
803 | padding: 40px 0 20px;
804 | background-color: #f5f5f5;
805 | }
806 | .site-footer a,
807 | .site-footer .connect {
808 | color: #50c1e9;
809 | }
810 | .site-footer a:hover,
811 | .site-footer a:active {
812 | color: #50c1e9;
813 | text-decoration: none;
814 | }
815 | .site-footer .row .col-md-4 {
816 | margin-bottom: 20px;
817 | }
818 | .site-footer h3 {
819 | margin-bottom: 20px;
820 | font-weight: 600;
821 | font-size: 18px;
822 | }
823 | .site-footer ul {
824 | padding-left: 0;
825 | }
826 | .site-footer li {
827 | list-style: none;
828 | font-size: 14px;
829 | }
830 | .site-footer hr {
831 | margin: 20px 0;
832 | border: none;
833 | height: 1px;
834 | width: 100%;
835 | }
836 | .site-footer hr.dashed {
837 | border-top: 1px dashed #ccc;
838 | }
839 | .site-footer .icon {
840 | margin-right: 10px;
841 | font-size: 28px;
842 | }
843 | .site-footer form {
844 | padding: 0 !important;
845 | }
846 | .site-footer form label {
847 | font-size: 14px !important;
848 | font-weight: normal !important;
849 | }
850 | .site-footer input[type="email"] {
851 | margin-right: 10px;
852 | width: 75%;
853 | }
854 | .site-footer input[type="email"],
855 | .site-footer input[type="email"]:focus,
856 | .site-footer input[type="submit"],
857 | .site-footer input[type="submit"]:active,
858 | .site-footer input[type="submit"]:focus {
859 | border: none !important;
860 | }
861 | .site-footer .email,
862 | .site-footer .clear {
863 | float: left;
864 | }
865 | .site-footer .copyright p:last-child {
866 | margin-top: 10px;
867 | }
868 | .site-footer .copyright b {
869 | font-size: 16px;
870 | }
871 | .site-footer .download .download__infos {
872 | font-size: 18px;
873 | }
874 | .site-footer .download .btn {
875 | color: #000;
876 | }
877 | .site-footer .download .btn-primary {
878 | color: #fff;
879 | }
880 |
--------------------------------------------------------------------------------
/gui/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/gui/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/gui/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/gui/static/icons/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/icons/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/gui/static/icons/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/icons/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/gui/static/icons/apple-touch-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/icons/apple-touch-icon-144x144.png
--------------------------------------------------------------------------------
/gui/static/icons/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/icons/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/gui/static/icons/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/icons/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/gui/static/icons/apple-touch-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/icons/apple-touch-icon-57x57.png
--------------------------------------------------------------------------------
/gui/static/icons/apple-touch-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/icons/apple-touch-icon-72x72.png
--------------------------------------------------------------------------------
/gui/static/icons/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/icons/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/gui/static/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/gui/static/img/Jumbotron.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/Jumbotron.jpg
--------------------------------------------------------------------------------
/gui/static/img/ascii-logo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/ascii-logo.gif
--------------------------------------------------------------------------------
/gui/static/img/automated_payload_generation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/automated_payload_generation.png
--------------------------------------------------------------------------------
/gui/static/img/automatic_page_recon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/automatic_page_recon.png
--------------------------------------------------------------------------------
/gui/static/img/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/bg.png
--------------------------------------------------------------------------------
/gui/static/img/bootflat-ui-kit.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/bootflat-ui-kit.jpg
--------------------------------------------------------------------------------
/gui/static/img/canvas_rendered_screenshots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/canvas_rendered_screenshots.png
--------------------------------------------------------------------------------
/gui/static/img/correlated_injections.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/correlated_injections.png
--------------------------------------------------------------------------------
/gui/static/img/custom_xss_shortdomain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/custom_xss_shortdomain.png
--------------------------------------------------------------------------------
/gui/static/img/design.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/design.png
--------------------------------------------------------------------------------
/gui/static/img/divider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/divider.png
--------------------------------------------------------------------------------
/gui/static/img/easy_markdown_generation_reports.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/easy_markdown_generation_reports.png
--------------------------------------------------------------------------------
/gui/static/img/fast.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/fast.png
--------------------------------------------------------------------------------
/gui/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/favicon.ico
--------------------------------------------------------------------------------
/gui/static/img/feature-bootstrap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/feature-bootstrap.png
--------------------------------------------------------------------------------
/gui/static/img/feature-css.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/feature-css.png
--------------------------------------------------------------------------------
/gui/static/img/feature-lightweight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/feature-lightweight.png
--------------------------------------------------------------------------------
/gui/static/img/feature-mobile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/feature-mobile.png
--------------------------------------------------------------------------------
/gui/static/img/footer-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/footer-logo.png
--------------------------------------------------------------------------------
/gui/static/img/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/github.png
--------------------------------------------------------------------------------
/gui/static/img/index.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/index.png
--------------------------------------------------------------------------------
/gui/static/img/logo-index.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/logo-index.png
--------------------------------------------------------------------------------
/gui/static/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/logo.png
--------------------------------------------------------------------------------
/gui/static/img/manage_all_your_injections.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/manage_all_your_injections.png
--------------------------------------------------------------------------------
/gui/static/img/photo-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/photo-1.jpg
--------------------------------------------------------------------------------
/gui/static/img/photo-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/photo-2.jpg
--------------------------------------------------------------------------------
/gui/static/img/photo-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/photo-3.jpg
--------------------------------------------------------------------------------
/gui/static/img/photo-4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/photo-4.jpg
--------------------------------------------------------------------------------
/gui/static/img/prototyping.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/prototyping.png
--------------------------------------------------------------------------------
/gui/static/img/report_email_alerts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/report_email_alerts.png
--------------------------------------------------------------------------------
/gui/static/img/share.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/share.gif
--------------------------------------------------------------------------------
/gui/static/img/slider1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/slider1.jpg
--------------------------------------------------------------------------------
/gui/static/img/slider2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/slider2.jpg
--------------------------------------------------------------------------------
/gui/static/img/slider3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/slider3.jpg
--------------------------------------------------------------------------------
/gui/static/img/thumbnail-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/thumbnail-1.jpg
--------------------------------------------------------------------------------
/gui/static/img/thumbnail-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/thumbnail-2.jpg
--------------------------------------------------------------------------------
/gui/static/img/thumbnail-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/thumbnail-3.jpg
--------------------------------------------------------------------------------
/gui/static/img/thumbnail-4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/thumbnail-4.jpg
--------------------------------------------------------------------------------
/gui/static/img/together.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/together.png
--------------------------------------------------------------------------------
/gui/static/img/wild_flowers.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/wild_flowers.png
--------------------------------------------------------------------------------
/gui/static/img/xss-hunter-report.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/xss-hunter-report.png
--------------------------------------------------------------------------------
/gui/static/img/xss_frontpage_alert.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/img/xss_frontpage_alert.png
--------------------------------------------------------------------------------
/gui/static/js/clipboard.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * clipboard.js v1.5.5
3 | * https://zenorocha.github.io/clipboard.js
4 | *
5 | * Licensed MIT © Zeno Rocha
6 | */
7 | !function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.Clipboard=t()}}(function(){var t,e,n;return function t(e,n,r){function o(a,c){if(!n[a]){if(!e[a]){var s="function"==typeof require&&require;if(!c&&s)return s(a,!0);if(i)return i(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var l=n[a]={exports:{}};e[a][0].call(l.exports,function(t){var n=e[a][1][t];return o(n?n:t)},l,l.exports,t,e,n,r)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;ar;r++)n[r].fn.apply(n[r].ctx,e);return this},off:function(t,e){var n=this.e||(this.e={}),r=n[t],o=[];if(r&&e)for(var i=0,a=r.length;a>i;i++)r[i].fn!==e&&r[i].fn._!==e&&o.push(r[i]);return o.length?n[t]=o:delete n[t],this}},e.exports=r},{}],8:[function(t,e,n){"use strict";function r(t){return t&&t.__esModule?t:{"default":t}}function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}n.__esModule=!0;var i=function(){function t(t,e){for(var n=0;narticle,aside,figcaption,figure,footer,header,hgroup,nav,section{display:block}mark{background:#FF0;color:#000}";
6 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML=" ";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
7 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",version:"3.6.2pre",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment();
8 | for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d -1 || ua.indexOf('Presto') > -1) {
40 | return // Opera, which might pretend to be IE
41 | }
42 | var emulated = emulatedIEMajorVersion()
43 | if (emulated === null) {
44 | return // Not IE
45 | }
46 | var nonEmulated = actualNonEmulatedIEMajorVersion()
47 |
48 | if (emulated !== nonEmulated) {
49 | window.alert('WARNING: You appear to be using IE' + nonEmulated + ' in IE' + emulated + ' emulation mode.\nIE emulation modes can behave significantly differently from ACTUAL older versions of IE.\nPLEASE DON\'T FILE BOOTSTRAP BUGS based on testing in IE emulation modes!')
50 | }
51 | })();
52 |
--------------------------------------------------------------------------------
/gui/static/js/ie10-viewport-bug-workaround.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * IE10 viewport hack for Surface/desktop Windows 8 bug
3 | * Copyright 2014-2015 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 |
7 | // See the Getting Started docs for more information:
8 | // http://getbootstrap.com/getting-started/#support-ie10-width
9 |
10 | (function () {
11 | 'use strict';
12 |
13 | if (navigator.userAgent.match(/IEMobile\/10\.0/)) {
14 | var msViewportStyle = document.createElement('style')
15 | msViewportStyle.appendChild(
16 | document.createTextNode(
17 | '@-ms-viewport{width:auto!important}'
18 | )
19 | )
20 | document.querySelector('head').appendChild(msViewportStyle)
21 | }
22 |
23 | })();
24 |
--------------------------------------------------------------------------------
/gui/static/js/main.js:
--------------------------------------------------------------------------------
1 | /*
2 | * JavaScript for the general website
3 | */
4 | $( document ).ready(function() {
5 | $( ".loading_bar" ).fadeOut();
6 | prettyPrint();
7 | });
8 |
9 | function hide_loading_bar() {
10 | $( ".loading_bar" ).fadeOut();
11 | }
12 |
13 | function show_loading_bar() {
14 | $( ".loading_bar" ).fadeIn();
15 | }
16 |
17 | function set_loading_bar() {
18 |
19 | }
20 |
21 | /*
22 | * Main XSS Hunter JavaScript
23 | * ( for the actual app )
24 | */
25 | API_SERVER = "//" + document.domain.toString().replace( "www", "api" );
26 | CSRF_TOKEN = "";
27 | USER = {};
28 |
29 | function api_request( method, path, data, callback ) {
30 | show_loading_bar();
31 | header_data = {};
32 |
33 | if( CSRF_TOKEN != "" ) {
34 | header_data["X-CSRF-Token"] = CSRF_TOKEN;
35 | }
36 |
37 | if( method == "GET" ) {
38 | content_type = "application/x-www-form-urlencoded"
39 | send_data = $.param( data );
40 | } else {
41 | content_type = "application/json; charset=utf-8"
42 | send_data = JSON.stringify( data );
43 | }
44 |
45 | $.ajax({
46 | url: API_SERVER + path,
47 | type: method,
48 | headers: header_data,
49 | data: send_data,
50 | xhrFields: {
51 | withCredentials: true
52 | },
53 | contentType: content_type,
54 | dataType: "json",
55 | success: function (data) {
56 | hide_loading_bar();
57 | callback( data );
58 | },
59 | error: function (data) {
60 | hide_loading_bar();
61 | callback({
62 | "error": true,
63 | });
64 | }
65 | });
66 | }
67 |
--------------------------------------------------------------------------------
/gui/static/js/navbar.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/js/navbar.js
--------------------------------------------------------------------------------
/gui/static/js/register.js:
--------------------------------------------------------------------------------
1 | $( ".signup_button" ).click( function() {
2 | register_account();
3 | });
4 |
5 | $(function() {
6 | $('input[id=domain]').keyup(function() {
7 | if (this.value.match(/[^a-zA-Z0-9]/g)) {
8 | this.value = this.value.replace(/[^a-zA-Z0-9]/g, '');
9 | }
10 | });
11 | });
12 |
13 | function register_account() {
14 | USER = {}
15 | USER.username = $( "#username" ).val();
16 | USER.full_name = $( "#full_name" ).val();
17 | USER.domain = $( "#domain" ).val();
18 | USER.pgp_key = $( "#pgp-key" ).val();
19 | USER.email = $( "#email" ).val();
20 | USER.password = $( "#password" ).val();
21 | USER.invite_code = $( "#invite_code" ).val();
22 | api_request( "POST", "/api/register", USER, function( response ) {
23 | if( response["success"] == true ) {
24 | CSRF_TOKEN = response["csrf_token"];
25 | localStorage.setItem( "CSRF_TOKEN", CSRF_TOKEN );
26 | window.location = "/app";
27 | } else {
28 | $( ".bad_signup_text_fields" ).text( response["invalid_fields"] );
29 | $( ".bad_signup_dialogue" ).fadeIn();
30 | setTimeout( function() {
31 | $( ".bad_signup_dialogue" ).fadeOut();
32 | }, 5000);
33 | }
34 | });
35 | }
36 | $('[data-toggle="popover"]').popover({trigger: 'hover','placement': 'top'});
37 |
--------------------------------------------------------------------------------
/gui/static/js/respond.min.js:
--------------------------------------------------------------------------------
1 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
2 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */
3 | window.matchMedia=window.matchMedia||function(a){"use strict";var c,d=a.documentElement,e=d.firstElementChild||d.firstChild,f=a.createElement("body"),g=a.createElement("div");return g.id="mq-test-1",g.style.cssText="position:absolute;top:-100em",f.style.background="none",f.appendChild(g),function(a){return g.innerHTML='',d.insertBefore(f,e),c=42===g.offsetWidth,d.removeChild(f),{matches:c,media:a}}}(document);
4 |
5 | /*! Respond.js v1.1.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */
6 | (function(a){"use strict";function x(){u(!0)}var b={};if(a.respond=b,b.update=function(){},b.mediaQueriesSupported=a.matchMedia&&a.matchMedia("only all").matches,!b.mediaQueriesSupported){var q,r,t,c=a.document,d=c.documentElement,e=[],f=[],g=[],h={},i=30,j=c.getElementsByTagName("head")[0]||d,k=c.getElementsByTagName("base")[0],l=j.getElementsByTagName("link"),m=[],n=function(){for(var b=0;l.length>b;b++){var c=l[b],d=c.href,e=c.media,f=c.rel&&"stylesheet"===c.rel.toLowerCase();d&&f&&!h[d]&&(c.styleSheet&&c.styleSheet.rawCssText?(p(c.styleSheet.rawCssText,d,e),h[d]=!0):(!/^([a-zA-Z:]*\/\/)/.test(d)&&!k||d.replace(RegExp.$1,"").split("/")[0]===a.location.host)&&m.push({href:d,media:e}))}o()},o=function(){if(m.length){var b=m.shift();v(b.href,function(c){p(c,b.href,b.media),h[b.href]=!0,a.setTimeout(function(){o()},0)})}},p=function(a,b,c){var d=a.match(/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi),g=d&&d.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,"$1"+b+"$2$3")},i=!g&&c;b.length&&(b+="/"),i&&(g=1);for(var j=0;g>j;j++){var k,l,m,n;i?(k=c,f.push(h(a))):(k=d[j].match(/@media *([^\{]+)\{([\S\s]+?)$/)&&RegExp.$1,f.push(RegExp.$2&&h(RegExp.$2))),m=k.split(","),n=m.length;for(var o=0;n>o;o++)l=m[o],e.push({media:l.split("(")[0].match(/(only\s+)?([a-zA-Z]+)\s?/)&&RegExp.$2||"all",rules:f.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},s=function(){var a,b=c.createElement("div"),e=c.body,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",e||(e=f=c.createElement("body"),e.style.background="none"),e.appendChild(b),d.insertBefore(e,d.firstChild),a=b.offsetWidth,f?d.removeChild(e):e.removeChild(b),a=t=parseFloat(a)},u=function(b){var h="clientWidth",k=d[h],m="CSS1Compat"===c.compatMode&&k||c.body[h]||k,n={},o=l[l.length-1],p=(new Date).getTime();if(b&&q&&i>p-q)return a.clearTimeout(r),r=a.setTimeout(u,i),void 0;q=p;for(var v in e)if(e.hasOwnProperty(v)){var w=e[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?t||s():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?t||s():1)),w.hasquery&&(z&&A||!(z||m>=x)||!(A||y>=m))||(n[w.media]||(n[w.media]=[]),n[w.media].push(f[w.rules]))}for(var C in g)g.hasOwnProperty(C)&&g[C]&&g[C].parentNode===j&&j.removeChild(g[C]);for(var D in n)if(n.hasOwnProperty(D)){var E=c.createElement("style"),F=n[D].join("\n");E.type="text/css",E.media=D,j.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(c.createTextNode(F)),g.push(E)}},v=function(a,b){var c=w();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},w=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}();n(),b.update=n,a.addEventListener?a.addEventListener("resize",x,!1):a.attachEvent&&a.attachEvent("onresize",x)}})(this);
7 |
--------------------------------------------------------------------------------
/gui/static/video/xss_the_wrong_way.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/video/xss_the_wrong_way.mp4
--------------------------------------------------------------------------------
/gui/static/video/xss_the_wrong_way.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mandatoryprogrammer/xsshunter/e94d4ae389943db9a30901a6efc4d18c42c79732/gui/static/video/xss_the_wrong_way.webm
--------------------------------------------------------------------------------
/gui/templates/contact.htm:
--------------------------------------------------------------------------------
1 | {% include header.htm %}
2 |
3 | {% include navbar.htm %}
4 |
5 |
6 | ×
7 | Message Sent!
8 | Your message has been sent, thank you for your interest in XSSHunter! Please allow 48 hours for a response.
9 |
10 |
11 |
32 |
33 |
34 |
35 | {% include footer.htm %}
36 |
--------------------------------------------------------------------------------
/gui/templates/features.htm:
--------------------------------------------------------------------------------
1 | {% include header.htm %}
2 |
3 | {% include navbar.htm %}
4 |
5 |
103 |
104 |
105 | {% include footer.htm %}
106 |
--------------------------------------------------------------------------------
/gui/templates/footer.htm:
--------------------------------------------------------------------------------
1 |