Ensure the file manager properly escapes file & directory names. Fixes #3196

pull/9/head
Murtuza Zabuawala 2018-03-19 10:58:12 +00:00 committed by Dave Page
parent 48319d56df
commit 9ea118ca57
4 changed files with 139 additions and 21 deletions

View File

@ -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,
'&lt;img src=x onmouseover=alert("1")&gt;.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)

View File

@ -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) {

View File

@ -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();
}
});

View File

@ -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):