Ensure the file manager properly escapes file & directory names. Fixes #3196
parent
48319d56df
commit
9ea118ca57
|
@ -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/<img src=x onmouseover=alert("1")>.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)
|
|
@ -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 += '<li class="' + cap_classes +
|
||||
'"><div class="clip"><span data-alt="' +
|
||||
|
@ -550,7 +550,7 @@ define([
|
|||
} else {
|
||||
result +=
|
||||
'<p><input type="text" class="fm_file_rename" />' +
|
||||
'<span class="less_text" title="' + file_name_original + '">' + fm_filename +
|
||||
'<span class="less_text" title="' + fm_filename + '">' + fm_filename +
|
||||
'</span></p>';
|
||||
}
|
||||
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 += '<tr class="' + cap_classes + '">';
|
||||
|
||||
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 += '<td title="' + path + '" class="' + class_type + '">';
|
||||
if ((data[key]).Protected == 1) {
|
||||
|
@ -624,7 +624,7 @@ define([
|
|||
fm_filename + '</span></td>';
|
||||
} else {
|
||||
result += '<p><input type="text" class="fm_file_rename"/><span class="less_text" title="' +
|
||||
file_name_original + '">' + fm_filename + '</span></p></td>';
|
||||
fm_filename + '">' + fm_filename + '</span></p></td>';
|
||||
}
|
||||
if (props.Size && props.Size != '') {
|
||||
result += '<td><span title="' + props.Size + '">' +
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue