CVE-2018-19968
Description
An attacker can exploit phpMyAdmin before 4.8.4 to leak the contents of a local file because of an error in the transformation feature. The attacker must have access to the phpMyAdmin Configuration Storage tables, although these can easily be created in any database to which the attacker has access. An attacker must have valid credentials to log in to phpMyAdmin; this vulnerability does not allow an attacker to circumvent the login system.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
An authenticated user of phpMyAdmin before 4.8.4 can read arbitrary local files by exploiting an error in the transformation feature.
Vulnerability
In phpMyAdmin versions prior to 4.8.4, the transformEditedValues() method directly included a transformation plugin file based on user-controllable input without verifying that the file is a valid transformation plugin. The code path is reachable when an attacker has access to the phpMyAdmin Configuration Storage tables (the pma__column_info table) and can log in to phpMyAdmin with valid credentials. The commit 6a1ba61 changes the logic to check for the existence of the expected class before including and instantiating the plugin, preventing arbitrary file inclusion [1][2][4].
Exploitation
The attacker needs valid login credentials for phpMyAdmin. They must also have the ability to insert or modify rows in the pma__column_info table (part of the phpMyAdmin Configuration Storage). By crafting a malicious entry in that table with a file path to a local system file, and then triggering a transformation action on a column, include_once would load the specified file, causing its contents to be leaked to the attacker through subsequent output. No additional race condition or user interaction beyond the normal edit operation is required [1][4].
Impact
Successful exploitation allows an authenticated attacker to read arbitrary local files that the web server process has access to. This can lead to disclosure of database credentials, configuration files, or any other sensitive data present on the server. The attacker does not gain code execution or privilege escalation from this vulnerability alone [1][4].
Mitigation
Users should upgrade to phpMyAdmin 4.8.4 or later, which was released on 2018-12-07 and includes the fix [2][4]. For systems that cannot upgrade immediately, the recommended workaround is to ensure that phpMyAdmin Configuration Storage tables are not writable by untrusted users, but this only reduces, not eliminates, the risk. The vulnerability is not known to be listed in CISA's KEV as of the publication date [4].
AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
phpmyadmin/phpmyadminPackagist | < 4.8.4 | 4.8.4 |
Affected products
3- Range: <4.8.4
- ghsa-coords2 versions
< 4.8.4+ 1 more
- (no CPE)range: < 4.8.4
- (no CPE)range: < 5.1.1-1.2
Patches
16a1ba61e2900Remove transformation plugin includes
5 files changed · +152 −96
libraries/classes/Display/Results.php+19 −18 modified@@ -2893,28 +2893,29 @@ private function _getRowValues( if (@file_exists($include_file)) { - include_once $include_file; $class_name = Transformations::getClassName($include_file); - // todo add $plugin_manager - $plugin_manager = null; - $transformation_plugin = new $class_name( - $plugin_manager - ); + if (class_exists($class_name)) { + // todo add $plugin_manager + $plugin_manager = null; + $transformation_plugin = new $class_name( + $plugin_manager + ); - $transform_options = Transformations::getOptions( - isset( - $mime_map[$orgFullColName] + $transform_options = Transformations::getOptions( + isset( + $mime_map[$orgFullColName] + ['transformation_options'] + ) + ? $mime_map[$orgFullColName] ['transformation_options'] - ) - ? $mime_map[$orgFullColName] - ['transformation_options'] - : '' - ); + : '' + ); - $meta->mimetype = str_replace( - '_', '/', - $mime_map[$orgFullColName]['mimetype'] - ); + $meta->mimetype = str_replace( + '_', '/', + $mime_map[$orgFullColName]['mimetype'] + ); + } } // end if file_exists } // end if transformation is set
libraries/classes/InsertEdit.php+50 −48 modified@@ -2478,7 +2478,6 @@ public function transformEditedValues( ) { $include_file = 'libraries/classes/Plugins/Transformations/' . $file; if (is_file($include_file)) { - include_once $include_file; $_url_params = array( 'db' => $db, 'table' => $table, @@ -2492,20 +2491,22 @@ public function transformEditedValues( ); $transform_options['wrapper_link'] = Url::getCommon($_url_params); $class_name = Transformations::getClassName($include_file); - /** @var TransformationsPlugin $transformation_plugin */ - $transformation_plugin = new $class_name(); - - foreach ($edited_values as $cell_index => $curr_cell_edited_values) { - if (isset($curr_cell_edited_values[$column_name])) { - $edited_values[$cell_index][$column_name] - = $extra_data['transformations'][$cell_index] - = $transformation_plugin->applyTransformation( - $curr_cell_edited_values[$column_name], - $transform_options, - '' - ); - } - } // end of loop for each transformation cell + if (class_exists($class_name)) { + /** @var TransformationsPlugin $transformation_plugin */ + $transformation_plugin = new $class_name(); + + foreach ($edited_values as $cell_index => $curr_cell_edited_values) { + if (isset($curr_cell_edited_values[$column_name])) { + $edited_values[$cell_index][$column_name] + = $extra_data['transformations'][$cell_index] + = $transformation_plugin->applyTransformation( + $curr_cell_edited_values[$column_name], + $transform_options, + '' + ); + } + } // end of loop for each transformation cell + } } return $extra_data; } @@ -3268,42 +3269,43 @@ private function getHtmlForInsertEditFormColumn( $file = $column_mime['input_transformation']; $include_file = 'libraries/classes/Plugins/Transformations/' . $file; if (is_file($include_file)) { - include_once $include_file; $class_name = Transformations::getClassName($include_file); - $transformation_plugin = new $class_name(); - $transformation_options = Transformations::getOptions( - $column_mime['input_transformation_options'] - ); - $_url_params = array( - 'db' => $db, - 'table' => $table, - 'transform_key' => $column['Field'], - 'where_clause' => $where_clause - ); - $transformation_options['wrapper_link'] - = Url::getCommon($_url_params); - $current_value = ''; - if (isset($current_row[$column['Field']])) { - $current_value = $current_row[$column['Field']]; - } - if (method_exists($transformation_plugin, 'getInputHtml')) { - $transformed_html = $transformation_plugin->getInputHtml( - $column, - $row_id, - $column_name_appendix, - $transformation_options, - $current_value, - $text_dir, - $tabindex, - $tabindex_for_value, - $idindex + if (class_exists($class_name)) { + $transformation_plugin = new $class_name(); + $transformation_options = Transformations::getOptions( + $column_mime['input_transformation_options'] ); - } - if (method_exists($transformation_plugin, 'getScripts')) { - $GLOBALS['plugin_scripts'] = array_merge( - $GLOBALS['plugin_scripts'], - $transformation_plugin->getScripts() + $_url_params = array( + 'db' => $db, + 'table' => $table, + 'transform_key' => $column['Field'], + 'where_clause' => $where_clause ); + $transformation_options['wrapper_link'] + = Url::getCommon($_url_params); + $current_value = ''; + if (isset($current_row[$column['Field']])) { + $current_value = $current_row[$column['Field']]; + } + if (method_exists($transformation_plugin, 'getInputHtml')) { + $transformed_html = $transformation_plugin->getInputHtml( + $column, + $row_id, + $column_name_appendix, + $transformation_options, + $current_value, + $text_dir, + $tabindex, + $tabindex_for_value, + $idindex + ); + } + if (method_exists($transformation_plugin, 'getScripts')) { + $GLOBALS['plugin_scripts'] = array_merge( + $GLOBALS['plugin_scripts'], + $transformation_plugin->getScripts() + ); + } } } }
libraries/classes/Transformations.php+12 −10 modified@@ -181,33 +181,35 @@ public static function getClassName($filename) * * @param string $file transformation file * - * @return String the description of the transformation + * @return string the description of the transformation */ public static function getDescription($file) { $include_file = 'libraries/classes/Plugins/Transformations/' . $file; - /* @var $class_name PhpMyAdmin\Plugins\TransformationsInterface */ + /* @var $class_name \PhpMyAdmin\Plugins\TransformationsInterface */ $class_name = self::getClassName($include_file); - // include and instantiate the class - include_once $include_file; - return $class_name::getInfo(); + if (class_exists($class_name)) { + return $class_name::getInfo(); + } + return ''; } /** * Returns the name of the transformation * * @param string $file transformation file * - * @return String the name of the transformation + * @return string the name of the transformation */ public static function getName($file) { $include_file = 'libraries/classes/Plugins/Transformations/' . $file; - /* @var $class_name PhpMyAdmin\Plugins\TransformationsInterface */ + /* @var $class_name \PhpMyAdmin\Plugins\TransformationsInterface */ $class_name = self::getClassName($include_file); - // include and instantiate the class - include_once $include_file; - return $class_name::getName(); + if (class_exists($class_name)) { + return $class_name::getName(); + } + return ''; } /**
tbl_replace.php+21 −20 modified@@ -224,28 +224,29 @@ $filename = 'libraries/classes/Plugins/Transformations/' . $mime_map[$column_name]['input_transformation']; if (is_file($filename)) { - include_once $filename; $classname = Transformations::getClassName($filename); - /** @var IOTransformationsPlugin $transformation_plugin */ - $transformation_plugin = new $classname(); - $transformation_options = Transformations::getOptions( - $mime_map[$column_name]['input_transformation_options'] - ); - $current_value = $transformation_plugin->applyTransformation( - $current_value, $transformation_options - ); - // check if transformation was successful or not - // and accordingly set error messages & insert_fail - if (method_exists($transformation_plugin, 'isSuccess') - && !$transformation_plugin->isSuccess() - ) { - $insert_fail = true; - $row_skipped = true; - $insert_errors[] = sprintf( - __('Row: %1$s, Column: %2$s, Error: %3$s'), - $rownumber, $column_name, - $transformation_plugin->getError() + if (class_exists($classname)) { + /** @var IOTransformationsPlugin $transformation_plugin */ + $transformation_plugin = new $classname(); + $transformation_options = Transformations::getOptions( + $mime_map[$column_name]['input_transformation_options'] + ); + $current_value = $transformation_plugin->applyTransformation( + $current_value, $transformation_options ); + // check if transformation was successful or not + // and accordingly set error messages & insert_fail + if (method_exists($transformation_plugin, 'isSuccess') + && !$transformation_plugin->isSuccess() + ) { + $insert_fail = true; + $row_skipped = true; + $insert_errors[] = sprintf( + __('Row: %1$s, Column: %2$s, Error: %3$s'), + $rownumber, $column_name, + $transformation_plugin->getError() + ); + } } } }
test/classes/TransformationsTest.php+50 −0 modified@@ -287,4 +287,54 @@ public function fixupData() ), ); } + + /** + * Test for getDescription + * + * @param string $file transformation file + * @param string $expectedDescription expected description + * + * @dataProvider providerGetDescription + */ + public function testGetDescription($file, $expectedDescription) + { + $this->assertEquals($expectedDescription, Transformations::getDescription($file)); + } + + /** + * @return array + */ + public function providerGetDescription() + { + return [ + ['../../../../test', ''], + ['Input/Text_Plain_SqlEditor', 'Syntax highlighted CodeMirror editor for SQL.'], + ['Output/Text_Plain_Sql', 'Formats text as SQL query with syntax highlighting.'] + ]; + } + + /** + * Test for getName + * + * @param string $file transformation file + * @param string $expectedName expected name + * + * @dataProvider providerGetName + */ + public function testGetName($file, $expectedName) + { + $this->assertEquals($expectedName, Transformations::getName($file)); + } + + /** + * @return array + */ + public function providerGetName() + { + return [ + ['../../../../test', ''], + ['Input/Text_Plain_SqlEditor', 'SQL'], + ['Output/Text_Plain_Sql', 'SQL'] + ]; + } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-xc97-r49q-cxgcghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2018-19968ghsaADVISORY
- security.gentoo.org/glsa/201904-16ghsavendor-advisoryx_refsource_GENTOOWEB
- www.securityfocus.com/bid/106178ghsavdb-entryx_refsource_BIDWEB
- github.com/phpmyadmin/phpmyadmin/commit/6a1ba61e29002f0305a9322a8af4eaaeb11c0732ghsaWEB
- lists.debian.org/debian-lts-announce/2019/02/msg00003.htmlghsamailing-listx_refsource_MLISTWEB
- www.phpmyadmin.net/security/PMASA-2018-6ghsaWEB
- www.phpmyadmin.net/security/PMASA-2018-6/mitrex_refsource_CONFIRM
News mentions
0No linked articles in our index yet.