diff --git a/build.xml b/build.xml index 1d84087bfd..dbea85090d 100644 --- a/build.xml +++ b/build.xml @@ -86,6 +86,7 @@ diff --git a/doc/config.rst b/doc/config.rst index b46cec3159..998061ec64 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -119,6 +119,15 @@ Basic settings Show warning about incomplete translations on certain threshold. +.. config:option:: $cfg['AllowThirdPartyFraming'] + + :type: boolean + :default: false + + Setting this to ``true`` allows phpMyAdmin to be included inside a frame, + and is a potential security hole allowing cross-frame scripting attacks or + clickjacking. + Server connection settings -------------------------- diff --git a/import.php b/import.php index e1c9534f52..61e1608285 100644 --- a/import.php +++ b/import.php @@ -125,7 +125,7 @@ if ($_POST == array() && $_GET == array()) { */ if (! in_array( - $format, + $format, array( 'csv', 'ldi', @@ -606,12 +606,21 @@ if (isset($my_die)) { if ($go_sql) { // parse sql query require_once 'libraries/parse_analyze.inc.php'; - + PMA_executeQueryAndSendQueryResponse( $analyzed_sql_results, false, $db, $table, null, null, null, false, null, null, null, null, $goto, $pmaThemeImage, null, null, null, $sql_query, null, null ); +} else if ($result) { + $response = PMA_Response::getInstance(); + $response->isSuccess(true); + $response->addJSON('message', PMA_Message::success($msg)); + $response->addJSON('sql_query', PMA_Util::getMessage('', $sql_query)); +} else if ($result == false) { + $response = PMA_Response::getInstance(); + $response->isSuccess(false); + $response->addJSON('message', PMA_Message::error($msg)); } else { $active_page = $goto; include '' . $goto; diff --git a/js/cross_framing_protection.js b/js/cross_framing_protection.js new file mode 100644 index 0000000000..1e1caf0fd4 --- /dev/null +++ b/js/cross_framing_protection.js @@ -0,0 +1,9 @@ +/* vim: set expandtab sw=4 ts=4 sts=4: */ +/** + * Conditionally included if framing is not allowed + */ +if(self == top) { + document.documentElement.style.display = 'block' ; +} else { + top.location = self.location ; +} diff --git a/js/functions.js b/js/functions.js index ac5339ec8a..69415e6066 100644 --- a/js/functions.js +++ b/js/functions.js @@ -128,13 +128,13 @@ function PMA_current_version(data) { if (data && data.version && data.date) { var current = parseVersionString(pmaversion); - var latest = parseVersionString(data['version']); - var version_information_message = PMA_messages.strLatestAvailable + ' ' + escapeHtml(data['version']); + var latest = parseVersionString(data.version); + var version_information_message = PMA_messages.strLatestAvailable + ' ' + escapeHtml(data.version); if (latest > current) { var message = $.sprintf( PMA_messages.strNewerVersion, - escapeHtml(data['version']), - escapeHtml(data['date']) + escapeHtml(data.version), + escapeHtml(data.date) ); var htmlClass = 'notice'; if (Math.floor(latest / 10000) === Math.floor(current / 10000)) { @@ -334,10 +334,10 @@ function confirmQuery(theForm1, sqlQuery1) var do_confirm_re_2 = new RegExp('^\\s*DELETE\\s+FROM\\s', 'i'); var do_confirm_re_3 = new RegExp('^\\s*TRUNCATE\\s', 'i'); - if (do_confirm_re_0.test(sqlQuery1.value) - || do_confirm_re_1.test(sqlQuery1.value) - || do_confirm_re_2.test(sqlQuery1.value) - || do_confirm_re_3.test(sqlQuery1.value)) { + if (do_confirm_re_0.test(sqlQuery1.value) || + do_confirm_re_1.test(sqlQuery1.value) || + do_confirm_re_2.test(sqlQuery1.value) || + do_confirm_re_3.test(sqlQuery1.value)) { var message = (sqlQuery1.value.length > 100) ? sqlQuery1.value.substr(0, 100) + '\n ...' : sqlQuery1.value; @@ -1584,8 +1584,8 @@ function PMA_ajaxShowMessage(message, timeout) * to the created AJAX message */ var $retval = $( - '' ) .hide() @@ -1876,9 +1876,9 @@ function PMA_SQLPrettyPrint(string) } // Normal indentatin and spaces for everything else else { - if (! spaceExceptionsBefore[tokens[i][1]] - && ! (i > 0 && spaceExceptionsAfter[tokens[i - 1][1]]) - && output.charAt(output.length - 1) != ' ') { + if (! spaceExceptionsBefore[tokens[i][1]] && + ! (i > 0 && spaceExceptionsAfter[tokens[i - 1][1]]) && + output.charAt(output.length - 1) != ' ') { output += " "; } if (tokens[i][0] == 'keyword') { @@ -1889,15 +1889,15 @@ function PMA_SQLPrettyPrint(string) } // split columns in select and 'update set' clauses, but only inside statements blocks - if ((lastStatementPart == 'select' || lastStatementPart == 'where' || lastStatementPart == 'set') - && tokens[i][1] == ',' && blockStack[0] == 'statement') { + if ((lastStatementPart == 'select' || lastStatementPart == 'where' || lastStatementPart == 'set') && + tokens[i][1] == ',' && blockStack[0] == 'statement') { output += "\n" + tabs(indentLevel + 1); } // split conditions in where clauses, but only inside statements blocks - if (lastStatementPart == 'where' - && (tokens[i][1] == 'and' || tokens[i][1] == 'or' || tokens[i][1] == 'xor')) { + if (lastStatementPart == 'where' && + (tokens[i][1] == 'and' || tokens[i][1] == 'or' || tokens[i][1] == 'xor')) { if (blockStack[0] == 'statement') { output += "\n" + tabs(indentLevel + 1); @@ -2574,6 +2574,7 @@ AJAX.registerOnload('functions.js', function () { // Get the name of the column that is being edited var colname = $(this).closest('tr').find('input:first').val(); var title; + var i; // And use it to make up a title for the page if (colname.length < 1) { title = PMA_messages.enum_newColumnVals; @@ -2597,7 +2598,7 @@ AJAX.registerOnload('functions.js', function () { var values = []; var in_string = false; var curr, next, buffer = ''; - for (var i = 0; i < inputstring.length; i++) { + for (i = 0; i < inputstring.length; i++) { curr = inputstring.charAt(i); next = i == inputstring.length ? '' : inputstring.charAt(i + 1); if (! in_string && curr == "'") { @@ -2628,35 +2629,35 @@ AJAX.registerOnload('functions.js', function () { } // Add the parsed values to the editor var drop_icon = PMA_getImage('b_drop.png'); - for (var i = 0; i < values.length; i++) { - fields += "" - + "" - + "" - + drop_icon - + ""; + for (i = 0; i < values.length; i++) { + fields += "" + + "" + + "" + + drop_icon + + ""; } /** * @var dialog HTML code for the ENUM/SET dialog */ - var dialog = "
" - + "
" - + "" + title + "" - + "

" + PMA_getImage('s_notice.png') - + PMA_messages.enum_hint + "

" - + "" + fields + "
" - + "
" - + "
" - + "
" - + "
" - + "
" - + "
" - + "" - + "
" - + "
"; + var dialog = "
" + + "
" + + "" + title + "" + + "

" + PMA_getImage('s_notice.png') + + PMA_messages.enum_hint + "

" + + "" + fields + "
" + + "
" + + "
" + + "
" + + "
" + + "
" + + "
" + + "" + + "
" + + "
"; /** * @var Defines functions to be called when the buttons in * the buttonOptions jQuery dialog bar are pressed @@ -2724,11 +2725,11 @@ AJAX.registerOnload('functions.js', function () { while (num_new_rows--) { $enum_editor_dialog.find('.values') .append( - "" - + "" - + "" - + PMA_getImage('b_drop.png') - + "" + "" + + "" + + "" + + PMA_getImage('b_drop.png') + + "" ) .find('tr:last') .show('fast'); @@ -2983,9 +2984,12 @@ function PMA_getRowNumber(classlist) */ function PMA_set_status_label($element) { - var text = $element.css('display') == 'none' - ? '+ ' - : '- '; + var text; + if ($element.css('display') == 'none') { + text = '+ '; + } else { + text = '- '; + } $element.closest('.slide-wrapper').prev().find('span').text(text); } @@ -3326,8 +3330,8 @@ function PMA_slidingMessage(msg, $obj) // we might have to create a new DOM node. if ($('#PMA_slidingMessage').length === 0) { $('#page_content').prepend( - '' + '' ); } $obj = $('#PMA_slidingMessage'); @@ -3698,17 +3702,17 @@ AJAX.registerOnload('functions.js', function () { $(this).closest('.ui-dialog').find('.ui-button:first').click(); } }); // end $.live() - + var $elm = $('textarea[name="view[as]"]'); if ($elm.length > 0) { if (typeof CodeMirror != 'undefined') { syntaxHighlighter = CodeMirror.fromTextArea( - $elm[0], + $elm[0], { - lineNumbers: true, - matchBrackets: true, - indentUnit: 4, - mode: "text/x-mysql", + lineNumbers: true, + matchBrackets: true, + indentUnit: 4, + mode: "text/x-mysql", lineWrapping: true } ); @@ -3804,6 +3808,7 @@ $(function () { }); }); +var checkboxes_sel = "input.checkall:checkbox:enabled"; /** * Watches checkboxes in a form to set the checkall box accordingly */ @@ -3824,7 +3829,6 @@ var checkboxes_changed = function () { $checkall.prop({checked: false, indeterminate: false}); } }; -var checkboxes_sel = "input.checkall:checkbox:enabled"; $(checkboxes_sel).live("change", checkboxes_changed); $("input.checkall_box").live("change", function () { diff --git a/libraries/Header.class.php b/libraries/Header.class.php index 16d0bb6656..905093dec5 100644 --- a/libraries/Header.class.php +++ b/libraries/Header.class.php @@ -158,6 +158,12 @@ class PMA_Header $this->_scripts->addFile('jquery/jquery.ba-hashchange-1.3.js'); $this->_scripts->addFile('jquery/jquery.debounce-1.0.5.js'); $this->_scripts->addFile('jquery/jquery.menuResizer-1.0.js'); + + // Cross-framing protection + if ($GLOBALS['cfg']['AllowThirdPartyFraming'] === false) { + $this->_scripts->addFile('cross_framing_protection.js'); + } + $this->_scripts->addFile('rte.js'); // Here would not be a good place to add CodeMirror because @@ -450,6 +456,12 @@ class PMA_Header */ $GLOBALS['now'] = gmdate('D, d M Y H:i:s') . ' GMT'; if (! defined('TESTSUITE')) { + /* Prevent against ClickJacking by disabling framing */ + if (! $GLOBALS['cfg']['AllowThirdPartyFraming']) { + header( + 'X-Frame-Options: DENY' + ); + } header( "X-Content-Security-Policy: default-src 'self' " . 'https://www.google.com ' @@ -528,6 +540,7 @@ class PMA_Header $retval = ''; $retval .= ''; $retval .= ''; + $retval .= ''; return $retval; } diff --git a/libraries/Scripts.class.php b/libraries/Scripts.class.php index c0d64d8ab1..a3057d7e0b 100644 --- a/libraries/Scripts.class.php +++ b/libraries/Scripts.class.php @@ -129,6 +129,7 @@ class PMA_Scripts || strpos($filename, 'ajax.js') !== false || strpos($filename, 'navigation.js') !== false || strpos($filename, 'get_image.js.php') !== false + || strpos($filename, 'cross_framing_protection.js') !== false ) { return 0; } else { diff --git a/libraries/config.default.php b/libraries/config.default.php index a9bf023e5b..42b7c0f21d 100644 --- a/libraries/config.default.php +++ b/libraries/config.default.php @@ -83,6 +83,14 @@ $cfg['ReservedWordDisableWarning'] = false; */ $cfg['TranslationWarningThreshold'] = 80; +/** + * Allows phpMyAdmin to be included from a other document in a frame; + * setting this to true is a potential security hole + * + * @global boolean $cfg['AllowThirdPartyFraming'] + */ +$cfg['AllowThirdPartyFraming'] = false; + /** * The 'cookie' auth_type uses blowfish algorithm to encrypt the password. If * at least one server configuration uses 'cookie' auth_type, enter here a diff --git a/libraries/config/messages.inc.php b/libraries/config/messages.inc.php index 349967a1b2..5b9e66f104 100644 --- a/libraries/config/messages.inc.php +++ b/libraries/config/messages.inc.php @@ -16,6 +16,8 @@ if (!function_exists('__')) { $strConfigAllowArbitraryServer_desc = __('If enabled user can enter any MySQL server in login form for cookie auth'); $strConfigAllowArbitraryServer_name = __('Allow login to any MySQL server'); +$strConfigAllowThirdPartyFraming_desc = __('Enabling this allows a page located on a different domain to call phpMyAdmin inside a frame, and is a potential [strong]security hole[/strong] allowing cross-frame scripting attacks'); +$strConfigAllowThirdPartyFraming_name = __('Allow third party framing'); $strConfigAllowUserDropDatabase_name = __('Show "Drop database" link to normal users'); $strConfigblowfish_secret_desc = __('Secret passphrase used for encrypting cookies in [kbd]cookie[/kbd] authentication'); $strConfigblowfish_secret_name = __('Blowfish secret'); diff --git a/libraries/config/setup.forms.php b/libraries/config/setup.forms.php index 43d6569427..cca00e3048 100644 --- a/libraries/config/setup.forms.php +++ b/libraries/config/setup.forms.php @@ -145,7 +145,8 @@ $forms['Features']['Other_core_settings'] = array( 'VersionCheck', 'VersionCheckProxyUrl', 'VersionCheckProxyUser', - 'VersionCheckProxyPass' + 'VersionCheckProxyPass', + 'AllowThirdPartyFraming', ); $forms['Sql_queries']['Sql_queries'] = array( 'ShowSQL', diff --git a/libraries/import.lib.php b/libraries/import.lib.php index 9476b9331f..72657b17db 100644 --- a/libraries/import.lib.php +++ b/libraries/import.lib.php @@ -87,7 +87,7 @@ function PMA_importRunQuery($sql = '', $full = '', $controluser = false, ) { global $import_run_buffer, $go_sql, $complete_query, $display_query, $sql_query, $my_die, $error, $reload, - $last_query_with_results, + $last_query_with_results, $result, $msg, $skip_queries, $executed_queries, $max_sql_len, $read_multiply, $cfg, $sql_query_disabled, $db, $run_query, $is_superuser; $read_multiply = 1; diff --git a/libraries/server_privileges.lib.php b/libraries/server_privileges.lib.php index 543c121b21..53a84d6465 100644 --- a/libraries/server_privileges.lib.php +++ b/libraries/server_privileges.lib.php @@ -3195,7 +3195,7 @@ function PMA_getHtmlForListingUsersofAGroup($userGroup) if ($result) { if ($GLOBALS['dbi']->numRows($result) == 0) { $html_output .= '

' - . __('No users were found belonging to this user group') + . __('No users were found belonging to this user group.') . '

'; } else { $html_output .= '' @@ -3367,7 +3367,7 @@ function PMA_getHtmlToEditUserGroup($userGroup = null) . ''; if ($userGroup == null) { - $html_output .= ''; + $html_output .= ''; $html_output .= ''; $html_output .= '
'; } @@ -3399,7 +3399,7 @@ function PMA_getHtmlToEditUserGroup($userGroup = null) } $html_output .= _getTabList( - __('Sever level tabs'), 'server', $allowedTabs['server'] + __('Server level tabs'), 'server', $allowedTabs['server'] ); $html_output .= _getTabList( __('Database level tabs'), 'db', $allowedTabs['db'] diff --git a/libraries/string.inc.php b/libraries/string.inc.php index 7248e9456a..96273bc614 100644 --- a/libraries/string.inc.php +++ b/libraries/string.inc.php @@ -1,7 +1,7 @@ \ No newline at end of file +?> diff --git a/po/af.po b/po/af.po index ea97d30d1d..76333c1819 100644 --- a/po/af.po +++ b/po/af.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: phpMyAdmin 4.1-dev\n" "Report-Msgid-Bugs-To: phpmyadmin-devel@lists.sourceforge.net\n" -"POT-Creation-Date: 2013-07-12 16:06+0200\n" +"POT-Creation-Date: 2013-07-29 15:39+0200\n" "PO-Revision-Date: 2013-02-13 18:58+0200\n" "Last-Translator: Andrey Prokopenko \n" "Language-Team: Afrikaans