diff --git a/web/pgadmin/feature_tests/xss_checks_file_manager_test.py b/web/pgadmin/feature_tests/xss_checks_file_manager_test.py new file mode 100644 index 000000000..dfad314e4 --- /dev/null +++ b/web/pgadmin/feature_tests/xss_checks_file_manager_test.py @@ -0,0 +1,114 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2018, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import os +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from regression.python_test_utils import test_utils +from regression.feature_utils.base_feature_test import BaseFeatureTest + + +class CheckFileManagerFeatureTest(BaseFeatureTest): + """Tests to check file manager for XSS.""" + + scenarios = [ + ("Tests to check if File manager is vulnerable to XSS", + dict()) + ] + + def before(self): + connection = test_utils.get_db_connection( + self.server['db'], + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port'] + ) + test_utils.drop_database(connection, "acceptance_test_db") + test_utils.create_database(self.server, "acceptance_test_db") + self.page.add_server(self.server) + self.wait = WebDriverWait(self.page.driver, 10) + self.XSS_FILE = '/tmp/.sql' + # Remove any previous file + if os.path.isfile(self.XSS_FILE): + os.remove(self.XSS_FILE) + + def after(self): + self.page.close_query_tool('sql', False) + self.page.remove_server(self.server) + connection = test_utils.get_db_connection( + self.server['db'], + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port'] + ) + test_utils.drop_database(connection, "acceptance_test_db") + + def runTest(self): + self._navigate_to_query_tool() + self.page.fill_codemirror_area_with("SELECT 1;") + self._create_new_file() + self._open_file_manager_and_check_xss_file() + + def _navigate_to_query_tool(self): + self.page.toggle_open_tree_item(self.server['name']) + self.page.toggle_open_tree_item('Databases') + self.page.toggle_open_tree_item('acceptance_test_db') + self.page.open_query_tool() + + def _create_new_file(self): + self.page.find_by_id("btn-save").click() + self.page.wait_for_query_tool_loading_indicator_to_disappear() + self.wait.until(EC.presence_of_element_located( + ( + By.XPATH, + "//*[contains(string(), 'Show hidden files and folders? ')]" + ) + )) + # Set the XSS value in input + self.page.find_by_id("file-input-path").clear() + self.page.find_by_id("file-input-path").send_keys( + self.XSS_FILE + ) + # Save the file + self.page.click_modal('Save') + self.page.wait_for_query_tool_loading_indicator_to_disappear() + + def _open_file_manager_and_check_xss_file(self): + self.page.find_by_id("btn-load-file").click() + self.wait.until(EC.presence_of_element_located( + ( + By.XPATH, + "//*[contains(string(), 'Show hidden files and folders? ')]" + ) + )) + self.page.find_by_id("file-input-path").clear() + self.page.find_by_id("file-input-path").send_keys( + '/tmp/' + ) + self.page.find_by_id("file-input-path").send_keys( + Keys.RETURN + ) + contents = self.page.find_by_id("contents").get_attribute('innerHTML') + self.page.click_modal('Cancel') + self.page.wait_for_query_tool_loading_indicator_to_disappear() + self._check_escaped_characters( + contents, + '<img src=x onmouseover=alert("1")>.sql', + 'File manager' + ) + + def _check_escaped_characters(self, source_code, string_to_find, source): + # For XSS we need to search against element's html code + assert source_code.find( + string_to_find + ) != -1, "{0} might be vulnerable to XSS ".format(source) diff --git a/web/pgadmin/misc/file_manager/static/js/utility.js b/web/pgadmin/misc/file_manager/static/js/utility.js index 74ae4e401..249b0fe8d 100755 --- a/web/pgadmin/misc/file_manager/static/js/utility.js +++ b/web/pgadmin/misc/file_manager/static/js/utility.js @@ -533,9 +533,9 @@ define([ if (fm_filename.length > 15) { fm_filename = (data[key]).Filename.substr(0, 10) + '...'; } + fm_filename = _.escape(fm_filename); - var file_name_original = encodeURI((data[key]).Filename); - var file_path_orig = encodeURI((data[key]).Path); + var file_path_orig = _.escape((data[key]).Path); result += '
  • ' + - '' + fm_filename + + '' + fm_filename + '

    '; } if (props.Width && props.Width != '') { @@ -584,7 +584,7 @@ define([ Object.keys(data).sort(function keyOrder(x, y) { return pgAdmin.natural_sort(x.toLowerCase(), y.toLowerCase()); }).forEach(function(key) { - var path = encodeURI((data[key]).Path), + var path = _.escape((data[key]).Path), props = (data[key]).Properties, cap_classes = '', cap, class_type; @@ -606,13 +606,13 @@ define([ class_type = 'fa fa-file-text tbl_file'; } - var file_name_original = encodeURI((data[key]).Filename); result += ''; var fm_filename = (data[key]).Filename; if (fm_filename.length > 48) { fm_filename = (data[key]).Filename.substr(0, 48) + '...'; } + fm_filename = _.escape(fm_filename); result += ''; if ((data[key]).Protected == 1) { @@ -624,7 +624,7 @@ define([ fm_filename + '
    '; } else { result += '

    ' + fm_filename + '

    '; + fm_filename + '">' + fm_filename + '

    '; } if (props.Size && props.Size != '') { result += '' + @@ -1628,7 +1628,7 @@ define([ foldername = fname; // Add _ variable in URL for avoiding the caching $.getJSON( - pgAdmin.FileUtils.fileConnector + '?_=' + Date.now() + 'mode=addfolder&path=' + $('.currentpath').val() + '&name=' + foldername, + pgAdmin.FileUtils.fileConnector + '?_=' + Date.now() + '&mode=addfolder&path=' + $('.currentpath').val() + '&name=' + foldername, function(resp) { var result = resp.data.result; if (result.Code === 1) { diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js index a84daa313..ff5138d98 100644 --- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js @@ -2946,7 +2946,7 @@ define('tools.querytool', [ }, // Set panel title. - setTitle: function(title) { + setTitle: function(title, unsafe) { var self = this; if (self.is_new_browser_tab) { @@ -2954,7 +2954,10 @@ define('tools.querytool', [ } else { _.each(window.top.pgAdmin.Browser.docker.findPanels('frm_datagrid'), function(p) { if (p.isVisible()) { - p.title(decodeURIComponent(title)); + if(unsafe) { + title = _.escape(title); + } + p.title(title); } }); } @@ -3022,7 +3025,7 @@ define('tools.querytool', [ success: function(res) { self.gridView.query_tool_obj.setValue(res); self.gridView.current_file = e; - self.setTitle(self.gridView.current_file.split('\\').pop().split('/').pop()); + self.setTitle(self.gridView.current_file.split('\\').pop().split('/').pop(), true); self.trigger('pgadmin-sqleditor:loading-icon:hide'); // hide cursor $busy_icon_div.removeClass('show_progress'); @@ -3073,7 +3076,7 @@ define('tools.querytool', [ if (res.data.status) { alertify.success(gettext('File saved successfully.')); self.gridView.current_file = e; - self.setTitle(self.gridView.current_file.replace(/^.*[\\\/]/g, '')); + self.setTitle(self.gridView.current_file.replace(/^.*[\\\/]/g, ''), true); // disable save button on file save $('#btn-save').prop('disabled', true); $('#btn-file-menu-save').css('display', 'none'); @@ -3114,7 +3117,7 @@ define('tools.querytool', [ if (self.gridView.current_file) { var title = self.gridView.current_file.replace(/^.*[\\\/]/g, '') + ' *'; - self.setTitle(title); + self.setTitle(title, true); } else { if (self.is_new_browser_tab) { title = window.document.title + ' *'; @@ -3122,7 +3125,7 @@ define('tools.querytool', [ // Find the title of the visible panel _.each(window.top.pgAdmin.Browser.docker.findPanels('frm_datagrid'), function(p) { if (p.isVisible()) { - self.gridView.panel_title = p._title; + self.gridView.panel_title = $(p._title).text(); } }); diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py index 10ffa1d8f..be0f1c217 100644 --- a/web/regression/feature_utils/pgadmin_page.py +++ b/web/regression/feature_utils/pgadmin_page.py @@ -92,21 +92,22 @@ class PgadminPage: else: assert False, "'Tools -> Query Tool' menu did not enable." - def close_query_tool(self): + def close_query_tool(self, name="Query", prompt=True): self.driver.switch_to.default_content() tab = self.find_by_xpath( "//*[contains(@class,'wcPanelTab') and " - "contains(.,'" + "Query" + "')]") + "contains(.,'" + name + "')]") ActionChains(self.driver).context_click(tab).perform() self.find_by_xpath( "//li[contains(@class, 'context-menu-item')]/span[contains(text()," " 'Remove Panel')]").click() - self.driver.switch_to.frame( - self.driver.find_elements_by_tag_name("iframe")[0]) - time.sleep(.5) - self.click_element(self.find_by_xpath( - '//button[contains(@class, "ajs-button") and ' - 'contains(.,"Don\'t save")]')) + if prompt: + self.driver.switch_to.frame( + self.driver.find_elements_by_tag_name("iframe")[0]) + time.sleep(.5) + self.click_element(self.find_by_xpath( + '//button[contains(@class, "ajs-button") and ' + 'contains(.,"Don\'t save")]')) self.driver.switch_to.default_content() def close_data_grid(self):